<template>
  <div class="knowledge-graph" :class="{ 'fullscreen-mode': isFullscreen, 'dark-theme': isDarkTheme }">
    <!-- 全局加载状态 - 仅在初始加载时显示 -->
    <div v-if="isLoading && !graphData && !treeData" class="loading-overlay">
      <div class="loading-container">
        <div class="loading-spinner"></div>
        <div class="loading-text">正在加载知识图谱...</div>
      </div>
    </div>
    
    <!-- 错误提示 -->
    <div v-if="apiError" class="error-overlay">
      <div class="error-container">
        <div class="error-content">
          <div class="error-icon">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
              <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
            </svg>
          </div>
          <div class="error-info">
            <div class="error-title">加载失败</div>
            <div class="error-text">{{ apiError }}</div>
          </div>
          <button @click="fetchGraphData" class="retry-btn">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
              <path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
            </svg>
            重试
          </button>
        </div>
      </div>
    </div>
  <!--   <div class="graph-header">
      <div class="section-title">
        <div class="title-bar"></div>
        <h2>3D知识图谱</h2>
      </div>
      
      <div class="graph-controls">
        <div class="legend">
          <div class="legend-item">
            <div class="legend-color" style="background: #FFD700; box-shadow: 0 0 10px #FFD700;"></div>
            <span>一级节点</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background: #E74C3C; box-shadow: 0 0 8px #E74C3C;"></div>
            <span>二级节点</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background: #3498DB; box-shadow: 0 0 8px #3498DB;"></div>
            <span>三级节点</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background: #2ECC71; box-shadow: 0 0 8px #2ECC71;"></div>
            <span>四级节点</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background: #9B59B6; box-shadow: 0 0 8px #9B59B6;"></div>
            <span>五级节点</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background: #F39C12; box-shadow: 0 0 8px #F39C12;"></div>
            <span>六级节点</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background: #1ABC9C; box-shadow: 0 0 8px #1ABC9C;"></div>
            <span>七级节点</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background: #E67E22; box-shadow: 0 0 8px #E67E22;"></div>
            <span>八级节点</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background: #34495E; box-shadow: 0 0 8px #34495E;"></div>
            <span>九级节点</span>
          </div>
          <div class="legend-item">
            <div class="legend-color" style="background: #95A5A6; box-shadow: 0 0 8px #95A5A6;"></div>
            <span>十级节点</span>
          </div>
        </div>
      </div>
    </div> -->
    
    <div class="graph-container">
      <!-- 左侧企业库分类选择器 -->
      <div class="knowledge-selector" :class="{ collapsed: panelCollapsed }" :style="panelCollapsed ? { width: '48px' } : { width: panelWidth + 'px' }">
        <button class="collapse-btn" @click="togglePanel">
          <span :class="{ rotated: panelCollapsed }" class="collapse-icon">‹</span>
        </button>
        
        <!-- 拖拽手柄 -->
        <div 
          v-if="!panelCollapsed" 
          class="resize-handle" 
          @mousedown="startResize"
          @touchstart="startResize"
        ></div>
        
        <!-- 全屏模式下的展开按钮 -->
      

        <div class="selector-content" v-show="!panelCollapsed">
          <!-- 知识库分类标题 -->
          <div class="knowledge-title">
            <span>企业库分类</span>
          </div>

          <!-- 搜索框 -->
          <div class="knowledge-search">
            <div class="search-input-wrapper">
              <input
                type="text"
                placeholder="输入名称搜索..."
                class="knowledge-search-input"
                v-model="searchKeyword"
                @input="handleSearchInput"
                @keyup.enter="handleSearch"
                @focus="showSearchDropdown = true"
                @blur="handleSearchBlur"
              >
             <!--  <button class="knowledge-search-btn" @click="handleSearch">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                  <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
                </svg>
                搜索
              </button> -->
            </div>
            
            <!-- 搜索下拉框 -->
            <div v-if="showSearchDropdown && searchResults.length > 0" class="search-dropdown">
              <div 
                v-for="item in searchResults" 
                :key="item.entityId"
                class="search-dropdown-item"
                @click="selectSearchResult(item)"
              >
                {{ item.name }}
              </div>
            </div>
            
            <div v-if="searchKeyword" class="search-clear" @click="clearSearch">
              <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
                <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
              </svg>
              <span>清除</span>
            </div>
          </div>

          <!-- 知识库分类列表 -->
          <div class="knowledge-categories" ref="knowledgeCategoriesRef">
            <!-- 递归渲染分类树 -->
            <div
              v-for="category in filteredCategories"
              :key="category.id"
              class="category-tree"
            >
              <CategoryNode
                :node="category"
                :level="0"
                :selected-id="selectedKnowledge"
                :expanded-ids="expandedCategories"
                @select="handleNodeSelect"
                @toggle="handleNodeToggle"
              />
            </div>
          </div>
        </div>
      </div>

      <!-- 右侧图谱区域 -->
      <div class="graph-right" :style="{ width: panelCollapsed ? 'calc(100% - 48px)' : `calc(100% - ${panelWidth}px)` }">
        <!-- 右侧图谱加载状态 -->
        <div v-if="isLoading && (graphData || treeData)" class="graph-loading-overlay">
          <div class="graph-loading-container">
            <div class="loading-spinner"></div>
            <div class="loading-text">正在更新图谱...</div>
          </div>
        </div>
        
        <div class="graph-chart" ref="chartRef">
          <!-- Vis-Network 2D图谱容器 -->
          <div 
            v-show="!is3DMode" 
            ref="visContainer" 
            class="vis-container"
          ></div>
        </div>
        <div class="controls-overlay">
          <div class="control-group">
            <!-- 主题切换按钮 -->
            <button @click="toggleTheme" class="control-btn theme-toggle">
              <svg v-if="!isDarkTheme" width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -6.56 -3.046A7.5 7.5 0 0 0 12 3Z"/>
              </svg>
              <svg v-else width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                <path d="M12 2.25a.75.75 0 0 1 .75.75v2.25a.75.75 0 0 1-1.5 0V3a.75.75 0 0 1 .75-.75ZM7.5 12a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM18.894 6.166a.75.75 0 0 0-1.06-1.06l-1.591 1.59a.75.75 0 1 0 1.06 1.061l1.591-1.59ZM21.75 12a.75.75 0 0 1-.75.75h-2.25a.75.75 0 0 1 0-1.5H21a.75.75 0 0 1 .75.75ZM17.834 18.894a.75.75 0 0 0 1.06-1.06l-1.59-1.591a.75.75 0 1 0-1.061 1.06l1.59 1.591ZM12 18a.75.75 0 0 1 .75.75V21a.75.75 0 0 1-1.5 0v-2.25A.75.75 0 0 1 12 18ZM6.166 17.834a.75.75 0 0 0-1.06 1.06l1.59 1.591a.75.75 0 0 0 1.061-1.06l-1.59-1.591ZM6 12a.75.75 0 0 1-.75.75H3a.75.75 0 0 1 0-1.5h2.25A.75.75 0 0 1 6 12ZM6.166 6.166a.75.75 0 0 0 1.06-1.06L5.636 3.515a.75.75 0 0 0-1.061 1.06l1.591 1.59Z"/>
              </svg>
              {{ isDarkTheme ? '白色主题' : '星空主题' }}
            </button>
            
            <!-- 全屏按钮 -->
            <button @click="toggleFullscreen" class="control-btn fullscreen-btn">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                <path v-if="!isFullscreen" d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
                <path v-else d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
              </svg>
              {{ isFullscreen ? '退出全屏' : '全屏显示' }}
            </button>
            <!-- 2D/3D切换按钮 -->
            <button @click="toggleGraphMode" class="control-btn mode-toggle">
              {{ is3DMode ? '切换到2D' : '切换到3D' }}
            </button>
            <template v-if="is3DMode">
              <button @click="resetCamera" class="control-btn">重置视角</button>
              <button @click="toggleAutoRotate" class="control-btn">{{ autoRotate ? '停止旋转' : '开始旋转' }}</button>
            </template>
            <template v-else>
              <button @click="reset2DView" class="control-btn">重置视图</button>
              <button @click="toggle2DAnimation" class="control-btn">{{ is2DAnimating ? '停止动画' : '开始动画' }}</button>
            </template>
            <!-- 左侧面板展开按钮 -->
            <button 
              v-if="panelCollapsed" 
              @click="togglePanel" 
              class="control-btn expand-panel-btn"
              style="background: #5B7CFF; color: white; border: none;"
            >
              展开面板
            </button>
          </div>
        </div>
      </div>
    </div>

    <!-- 意识形态教育链接抽屉 -->
    <el-drawer
      v-model="drawerVisible"
      title="知识点"
      direction="rtl"
      size="50%"
      :before-close="() => drawerVisible = false"
      class="knowledge-drawer"
    >
    <template #header>
      <div class="drawer-header">
        <div class="drawer-title">
          <div class="knowledge-header">
            <h2 class="knowledge-title">{{ currentNodeName }}</h2>
            <el-tag type="primary" round>知识点</el-tag>
          </div>
        </div>
      </div>
    </template>
      <div v-if="drawerLoading" class="drawer-loading">
        <div class="loading-spinner"></div>
        <p>正在加载数据...</p>
      </div>
      <div v-else-if="drawerData" class="drawer-content">
        <div v-if="drawerData.error" class="error-message">
          <el-alert
            :title="drawerData.error"
            type="error"
            show-icon
            :closable="false"
          />
        </div>
        <div v-else class="knowledge-content">
          
          <!-- 知识点描述 -->
          <div class="knowledge-description">
            <h3>知识点描述</h3>
            <p>{{ drawerData?.describe || getKnowledgeDescription() }}</p>
          </div>
          
          <!-- 推荐内容 -->
          <div class="recommended-content">
            <h3>推荐内容</h3>
            <div class="content-list">
              <div 
                v-for="(item, index) in (Array.isArray(drawerData.list) ? drawerData.list : [])" 
                :key="index"
                class="content-item"
              >
                <div class="item-number">{{ index + 1 }}、</div>
                <div class="item-content">
                  <div class="item-title">
                    <a :href="item.url" target="_blank" class="title-link">
                      {{ item.title }}
                    </a>
                  </div>
                  <div class="item-source">{{ item.site_name }}</div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </el-drawer>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, nextTick } from 'vue'
import * as THREE from 'three'
import { Network } from 'vis-network'
// @ts-ignore
import { DataSet } from 'vis-data'
import CategoryNode from '@/components/CategoryNode.vue'
import { getGraph, getGraphByEntityId, getIdeologyEducationLinks, getDocumentByName, type GraphNode, type GraphResponse, type SearchDocumentItem } from '@/api/knowledge/api'

const chartRef = ref<HTMLElement>()
const visContainer = ref<HTMLElement>()
const knowledgeCategoriesRef = ref<HTMLElement>()
const panelCollapsed = ref(false)
const panelWidth = ref(300) // 左侧面板宽度
const isResizing = ref(false) // 是否正在拖拽
const is3DMode = ref(true) // 默认使用3D模式
const is2DAnimating = ref(true) // 2D动画状态
const isDarkTheme = ref(true) // 主题状态：false为白色主题，true为星空主题
const isFullscreen = ref(false) // 全屏状态

// 3D相关变量
let scene: THREE.Scene | undefined
let camera: THREE.PerspectiveCamera | undefined
let renderer: THREE.WebGLRenderer | undefined
let controls: any
let animationId: number | undefined
let autoRotate = ref(true)

// Vis-Network 2D相关变量
let visNetwork: Network | null = null
let visNodes: DataSet<any> | null = null
let visEdges: DataSet<any> | null = null
let animationTimer: number | null = null // 2D动画定时器

// 知识库选择相关数据
const searchKeyword = ref('')
const selectedKnowledge = ref<string>('') // 当前选中的分类
const expandedCategories = ref<string[]>([])
const focusedNode = ref<string>('') // 当前聚焦的节点，用于筛选显示

// 搜索相关状态
const showSearchDropdown = ref(false)
const searchResults = ref<SearchDocumentItem[]>([])
const searchLoading = ref(false)

// API数据状态
const graphData = ref<any>(null)
const treeData = ref<GraphNode | null>(null)
const isLoading = ref(false)
const apiError = ref<string | null>(null)

// 抽屉状态
const drawerVisible = ref(false)
const drawerData = ref<any>(null)
const drawerLoading = ref(false)
const currentNodeName = ref<string>('')

// 3D对象存储
let starNodes: THREE.Group[] = []
let connectionLines: THREE.Line[] = []
let starGroup: THREE.Group | undefined
let lineGroup: THREE.Group | undefined

// 右侧图谱渲染数据映射（按 id 存右侧已生成的节点数据）
let rightGraphNodeMap: Map<string, any> = new Map()

// 悬浮效果相关
let hoveredSphere: THREE.Group | null = null
let tooltipElement: HTMLElement | null = null
let raycaster: THREE.Raycaster | undefined
let mouse: THREE.Vector2 | undefined
let isResetting = false // 添加重置状态标志
let resetCount = 0 // 添加重置计数器

// 类型定义
interface KnowledgeItem {
  id: string
  name: string
  children?: KnowledgeItem[]
}

interface KnowledgeCategory {
  id: string
  name: string
  icon: string
  clickFlag?: boolean
  point?: boolean
  children?: KnowledgeItem[]
}

// 转换API数据为组件需要的格式
const apiKnowledgeCategories = computed(() => {

  
  if (!treeData.value) {
    return []
  }
  
  const convertNode = (node: GraphNode): KnowledgeCategory => {
    return {
      id: node.entityId || node.id || node.name, // 优先使用entityId
      name: node.name,
      icon: node.tag || '',
      children: node.children ? node.children.map(convertNode) : []
    }
  }
  
  const result = [convertNode(treeData.value)]
  return result
})

// 企业库分类数据
const knowledgeCategories = ref<KnowledgeCategory[]>([

])

// 调试：检查数据结构

// 搜索相关函数
const handleSearchInput = async () => {
  const keyword = searchKeyword.value.trim()
  if (keyword.length < 2) {
    searchResults.value = []
    showSearchDropdown.value = false
    return
  }
  
  try {
    searchLoading.value = true
    const response = await getDocumentByName(keyword)
    
    // 处理不同的响应格式
    if (Array.isArray(response)) {
      // 如果直接返回数组
      searchResults.value = response
    } else if (response && response.data && Array.isArray(response.data)) {
      // 如果返回 { data: [...] } 格式
      searchResults.value = response.data
    } else if (response && response.code === 200 && response.data) {
      // 如果返回 { code: 200, data: [...] } 格式
      searchResults.value = response.data
    } else {
      searchResults.value = []
    }
    
    showSearchDropdown.value = searchResults.value.length > 0
  } catch (error) {
    searchResults.value = []
    showSearchDropdown.value = false
  } finally {
    searchLoading.value = false
  }
}

const handleSearchBlur = () => {
  // 延迟隐藏下拉框，让点击事件能够触发
  setTimeout(() => {
    showSearchDropdown.value = false
  }, 200)
}

const selectSearchResult = async (item: SearchDocumentItem) => {
  // 隐藏下拉框
  showSearchDropdown.value = false
  
  
  // 根据entityId定位到树数据并调用图谱接口
  await handleNodeSelectByEntityId(item.entityId)
  
  
  // 定位到树中的对应节点
  await locateTreeNodeByEntityId(item.entityId)
  
  // 设置搜索框显示选中的名称
  searchKeyword.value = item.name
}

const handleSearch = () => {
  // 搜索逻辑已经在 filteredCategories 中实现
}

const clearSearch = () => {
  searchKeyword.value = ''
  searchResults.value = []
  showSearchDropdown.value = false
}

// 获取分类数据（不进行过滤，保持完整树结构）
const filteredCategories = computed(() => {
  
  // 优先使用API数据，如果没有则使用默认数据
  const categories = apiKnowledgeCategories.value.length > 0 
    ? apiKnowledgeCategories.value 
    : knowledgeCategories.value
  
  
  // 始终返回完整的树结构，不进行过滤
  return categories
})

// 根据层级获取颜色
const getColorByLevel = (level: number): string => {
  const colors = [
    '#FFD700', // 一级：金色（中心星）
    '#E74C3C', // 二级：优雅红色
    '#3498DB', // 三级：天空蓝
    '#2ECC71', // 四级：翠绿色
    '#9B59B6', // 五级：优雅紫色
    '#F39C12', // 六级：温暖橙色
    '#1ABC9C', // 七级：青绿色
    '#E67E22', // 八级：深橙色
    '#34495E', // 九级：深蓝灰
    '#95A5A6'  // 十级：银灰色
  ]
  const color = colors[level - 1] || colors[colors.length - 1]
  return color
}

// 根据层级获取大小
const getSizeByLevel = (level: number): number => {
  const sizes = [
    40, // 一级：最大（中心星）
    32, // 二级：较大
    25, // 三级：大
    20, // 四级：中上
    16, // 五级：中等
    13, // 六级：中小
    10, // 七级：小
    4,  // 八级：较小
    3,  // 九级：很小
    2   // 十级：最小
  ]
  const size = sizes[level - 1] || sizes[sizes.length - 1]
  return size
}

// 注意：这些函数保留以备将来扩展使用
// createSphereGeometry 和 createGlowMaterial 函数已被内联到 createSphereNode 中

  // 创建带文字的球形节点 - 重新实现
const createSphereNode = (_position: THREE.Vector3, color: string, size: number, _level: number, text: string): THREE.Group => {
  const group = new THREE.Group()
  
  // 创建球体 - 增强3D立体感
  const geometry = new THREE.SphereGeometry(size, 32, 32)
  const material = new THREE.MeshPhongMaterial({
    color: color,
    transparent: true,
    opacity: 0.85,
    shininess: 100, // 增加光泽度
    specular: 0xffffff, // 白色高光
    emissive: color,
    emissiveIntensity: 0.1 // 适度的自发光
  })
  const sphere = new THREE.Mesh(geometry, material)
  group.add(sphere)
  
  // 创建文字 - 放在球体中心
  const canvas = document.createElement('canvas')
  const context = canvas.getContext('2d')!
  
  // 设置字体和测量文字 - 超大字体
  const fontSize = Math.max(60, size * 1.5) // 超大字体，至少60px
  const fontFamily = 'Microsoft YaHei, Arial, sans-serif'
  context.font = `bold ${fontSize}px ${fontFamily}`
  const metrics = context.measureText(text)
  const textWidth = metrics.width
  
  // 设置画布大小 - 为超大字体留更多空间
  canvas.width = Math.max(textWidth + 80, size * 4) // 超大画布
  canvas.height = Math.max(fontSize + 40, size * 2)
  
  // 清除画布并重新设置字体
  context.clearRect(0, 0, canvas.width, canvas.height)
  context.font = `bold ${fontSize}px ${fontFamily}`
  context.textAlign = 'center'
  context.textBaseline = 'middle'
  
  // 绘制更粗的黑色描边
  context.strokeStyle = '#000000'
  context.lineWidth = 12
  context.strokeText(text, canvas.width / 2, canvas.height / 2)
  
  // 绘制白色文字 - 多次绘制增强清晰度
  context.fillStyle = '#FFFFFF'
  context.fillText(text, canvas.width / 2, canvas.height / 2)
  context.fillText(text, canvas.width / 2, canvas.height / 2)
  context.fillText(text, canvas.width / 2, canvas.height / 2)
  
  // 创建纹理和精灵
  const texture = new THREE.CanvasTexture(canvas)
  const spriteMaterial = new THREE.SpriteMaterial({ 
    map: texture,
    transparent: true,
    depthTest: false
  })
  const sprite = new THREE.Sprite(spriteMaterial)
  
  // 设置精灵位置和大小 - 放在球体中心，超大缩放
  sprite.position.set(0, 0, 0) // 球体中心
  sprite.scale.set(canvas.width / 6, canvas.height / 6, 1) // 超大缩放
  sprite.renderOrder = 10 // 确保在最前面
  
  group.add(sprite)


  group.userData = {
    id: text,
    name: text,
      color: color,
    originalColor: color
  }

  return group
}

// 注意：这些函数保留以备将来扩展使用，但在当前实现中被内联
// createNodeLabel 和 createConnectionLine 函数已被内联到相应的地方

// 节点位置优化函数 - 避免重叠
const optimizeNodePositions = (nodes: any[]) => {
  const minDistance = 150 // 进一步增加最小距离，让节点分布更宽松
  const maxIterations = 15 // 增加最大迭代次数
  
  for (let iteration = 0; iteration < maxIterations; iteration++) {
    let hasOverlap = false
    
    for (let i = 0; i < nodes.length; i++) {
      for (let j = i + 1; j < nodes.length; j++) {
        const node1 = nodes[i]
        const node2 = nodes[j]
        
        // 计算距离
        const dx = node1.position.x - node2.position.x
        const dy = node1.position.y - node2.position.y
        const dz = node1.position.z - node2.position.z
        const distance = Math.sqrt(dx * dx + dy * dy + dz * dz)
        
        // 如果距离太近，调整位置
        if (distance < minDistance && distance > 0) {
          hasOverlap = true
          const pushDistance = (minDistance - distance) / 2
          const pushX = (dx / distance) * pushDistance
          const pushY = (dy / distance) * pushDistance
          const pushZ = (dz / distance) * pushDistance
          
          // 调整位置
          node1.position.x += pushX
          node1.position.y += pushY
          node1.position.z += pushZ
          
          node2.position.x -= pushX
          node2.position.y -= pushY
          node2.position.z -= pushZ
        }
      }
    }
    
    // 如果没有重叠，提前结束
    if (!hasOverlap) break
  }
}

// 自动调整相机位置以适应所有节点
const adjustCameraToFitNodes = (nodes: any[]) => {
  if (!camera || !controls || nodes.length === 0) return
  
  // 计算所有节点的边界框
  let minX = Infinity, maxX = -Infinity
  let minY = Infinity, maxY = -Infinity
  let minZ = Infinity, maxZ = -Infinity
  
  nodes.forEach(node => {
    const pos = node.position
    minX = Math.min(minX, pos.x)
    maxX = Math.max(maxX, pos.x)
    minY = Math.min(minY, pos.y)
    maxY = Math.max(maxY, pos.y)
    minZ = Math.min(minZ, pos.z)
    maxZ = Math.max(maxZ, pos.z)
  })
  
  // 计算中心点和最大距离
  const centerX = (minX + maxX) / 2
  const centerY = (minY + maxY) / 2
  const centerZ = (minZ + maxZ) / 2
  
  // 使用统一的相机位置 - 扩大视角，让节点分布更清晰
  camera.position.set(500, 500, 1500)
  camera.lookAt(centerX, centerY, centerZ)
  controls.update()
}

// 生成3D图谱数据
const generate3DGraphData = () => {
  const nodes: any[] = []
  const links: any[] = []

  // 优先使用API数据
  if (graphData.value) {

    generateApiGraphData(graphData.value, nodes, links)
  } else if (!selectedKnowledge.value) {
    // 未选中任何分类：显示默认三级结构

    generateDefaultThreeLevelGraph(nodes, links)
  } else {
    // 选中了分类：显示该分类的所有层级
    const selectedCategory = findCategoryById(selectedKnowledge.value)
    if (selectedCategory) {
      generateCategoryGraph(selectedCategory, nodes, links)
    } else {
      // 如果找不到选中的分类，回到默认视图
      generateDefaultThreeLevelGraph(nodes, links)
    }
  }


  // 如果有聚焦节点，显示该节点及其所有层级的子节点
  if (focusedNode.value) {
    const filteredNodes: any[] = []
    const filteredLinks: any[] = []
    const nodeIds = new Set<string>()

    // 递归函数：获取所有子节点（所有层级）
    const addAllChildren = (parentId: string) => {
      const childLinks = links.filter(link => link.source === parentId)
      childLinks.forEach(link => {
        const childNode = nodes.find(node => node.id === link.target)
        if (childNode && !nodeIds.has(childNode.id)) {
          filteredNodes.push(childNode)
          nodeIds.add(childNode.id)
          filteredLinks.push(link)
          
          // 递归添加这个子节点的所有子节点
          addAllChildren(childNode.id)
        }
      })
    }

    // 找到聚焦节点
    const focusNode = nodes.find(node => node.id === focusedNode.value)
    if (focusNode) {
      filteredNodes.push(focusNode)
      nodeIds.add(focusNode.id)

      // 递归添加所有子节点（所有层级）
      addAllChildren(focusedNode.value)

      // 如果聚焦节点是子节点，也显示其父节点
      const parentLinks = links.filter(link => link.target === focusedNode.value)
      parentLinks.forEach(link => {
        const parentNode = nodes.find(node => node.id === link.source)
        if (parentNode && !nodeIds.has(parentNode.id)) {
          filteredNodes.push(parentNode)
          nodeIds.add(parentNode.id)
          filteredLinks.push(link)
        }
      })
    }

    return { nodes: filteredNodes || [], links: filteredLinks || [] }
  }

  // 确保总是返回有效的数组
  return { nodes: nodes || [], links: links || [] }
}

// 从API数据生成图谱数据
const generateApiGraphData = (apiGraphData: GraphNode, nodes: any[], links: any[]) => {
  // 颜色和大小配置
  const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57', '#FF9FF3', '#54A0FF', '#5F27CD', '#00D2D3', '#FF6348']
  const sizes = [60, 45, 35, 30, 25, 20, 16, 12, 10, 8]
  
  const processNode = (node: GraphNode, level: number = 1, parentId?: string, startAngle: number = 0, radius: number = 200, parentPosition?: THREE.Vector3) => {
    const nodeId = (node as any).entityId || (node as any).id || node.name
    
    // 计算节点位置 - 优化的3D球体分布算法
    let x = 0, y = 0, z = 0
    let sphereRadius = 200 // 默认半径
    
    if (level === 1) {
      // 根节点在中心
      x = 0
      y = 0
      z = 0
      sphereRadius = 0
    } else {
      // 根据层级设置半径 - 球体分布
      sphereRadius = 250 + (level - 2) * 220 // 每层增加半径，让分布更宽松
      
      // 获取父节点位置
      const parentPos = parentPosition || new THREE.Vector3(0, 0, 0)
      
      // 计算父节点的球面坐标
      const parentRadius = Math.sqrt(parentPos.x * parentPos.x + parentPos.y * parentPos.y + parentPos.z * parentPos.z)
      const parentPhi = Math.acos(parentPos.z / Math.max(parentRadius, 0.001))
      const parentTheta = Math.atan2(parentPos.y, parentPos.x)
      
      // 子节点围绕父节点方向分布
      const childCount = (node as any).siblingCount || 1
      const childIndex = (node as any).siblingIndex || 0
      
      // 使用真正的3D球状分布算法 - 确保从任何角度观看都是完美的球状
      // 基于斐波那契螺旋在球面上的均匀分布
      const goldenRatio = (1 + Math.sqrt(5)) / 2
      const spiralAngle = childIndex * goldenRatio * Math.PI * 2
      
      // 在父节点周围创建球面分布，使用更合理的角度范围
      const spreadAngle = Math.PI / 6 // 在父节点周围±30度范围内分布
      const phiOffset = spreadAngle * Math.sin(spiralAngle) * (childIndex / Math.max(childCount - 1, 1))
      const thetaOffset = (Math.PI * 2 / childCount) * childIndex + spreadAngle * Math.cos(spiralAngle)
      
      // 计算子节点的球面坐标，确保在父节点附近形成球面分布
      const phi = Math.max(0.1, Math.min(Math.PI - 0.1, parentPhi + phiOffset))
      const theta = parentTheta + thetaOffset
      
      // 球面坐标转笛卡尔坐标 - 确保真正的3D球体分布
      x = sphereRadius * Math.sin(phi) * Math.cos(theta)
      y = sphereRadius * Math.sin(phi) * Math.sin(theta)
      z = sphereRadius * Math.cos(phi)
    }
    
    // 动态选择颜色和大小
    const colorIndex = Math.min(level - 1, colors.length - 1)
    const sizeIndex = Math.min(level - 1, sizes.length - 1)
    
    const nodeData = {
      id: nodeId,
      name: node.name,
      level: level,
      category: level,
      position: new THREE.Vector3(x, y, z),
      color: colors[colorIndex],
      size: sizes[sizeIndex],
      describe: node.describe,
      tag: node.tag,
      path: node.path,
      showFlag: node.showFlag,
      point: (node as any)?.point
    }
    
    nodes.push(nodeData)
    // 如果有父节点，创建连接
    if (parentId) {
      const link = {
        source: parentId,
        target: nodeId,
        level: level,
        color: colors[colorIndex]
      }
      links.push(link)
    }
    
    // 递归处理子节点
    if (node.children && node.children.length > 0) {
      // 为子节点添加父节点引用和子节点信息
      const childWithParent = node as any
      childWithParent.children = node.children
      
      node.children.forEach((child, index) => {
        // 为子节点添加兄弟节点信息
        const childWithSiblingInfo = child as any
        childWithSiblingInfo.siblingCount = node.children.length
        childWithSiblingInfo.siblingIndex = index
        
        // 传递当前节点位置作为父节点位置
        const currentPosition = new THREE.Vector3(x, y, z)
        processNode(child, level + 1, nodeId, startAngle, sphereRadius, currentPosition)
      })
    }
  }
  
  // 从根节点开始处理
  processNode(apiGraphData)
  
  // 节点位置优化 - 避免重叠
  optimizeNodePositions(nodes)
  
  // 自动调整相机位置以适应所有节点
  adjustCameraToFitNodes(nodes)
  
}

// 生成知识图谱数据 - 简化版本
const generateDefaultThreeLevelGraph = (nodes: any[], links: any[]) => {
  
  // 使用与filteredCategories相同的数据源逻辑
  const categories = apiKnowledgeCategories.value.length > 0 
    ? apiKnowledgeCategories.value 
    : knowledgeCategories.value
  
  if (!categories || categories.length === 0) {
    // 添加一个默认节点，避免空数据
    nodes.push({
      id: 'default',
      name: '默认节点',
      level: 0,
      position: { x: 0, y: 0, z: 0 },
      color: '#6366f1',
      size: 40,
      category: 0
    })
    return
  }

  // 扩展颜色和大小系统，支持更多层级
  const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57', '#FF9FF3', '#54A0FF', '#5F27CD', '#00D2D3', '#FF6348']
  const sizes = [60, 45, 35, 30, 25, 20, 16, 12, 10, 8]

  // 处理所有层级（如果有聚焦节点则显示完整层级，否则只显示3层）
  const processNodes = (items: any[], parentId: string | null, level: number, startAngle: number, radius: number, parentPosition?: THREE.Vector3) => {
    // 显示所有层级的数据，不限制层级
    if (!items || level > 10) return // 只设置合理上限防止无限递归
    
    items.forEach((item, index) => {
      // 优化的3D球体分布算法 - 按父节点方向分布
      const sphereRadius = 300 + level * 250 // 每层半径递增
      
      let x = 0, y = 0, z = 0
      
      if (level === 0) {
        // 根节点在中心
        x = 0
        y = 0
        z = 0
      } else {
        // 获取父节点位置
        const parentPos = parentPosition || new THREE.Vector3(0, 0, 0)
        
        // 计算父节点的球面坐标
        const parentRadius = Math.sqrt(parentPos.x * parentPos.x + parentPos.y * parentPos.y + parentPos.z * parentPos.z)
        const parentPhi = Math.acos(parentPos.z / Math.max(parentRadius, 0.001))
        const parentTheta = Math.atan2(parentPos.y, parentPos.x)
        
        // 使用改进的球面均匀分布算法 - 确保从任何角度观看都有良好效果
        
        // 使用改进的球面分布算法，在父节点方向上创建均匀分布
        const goldenRatio = (1 + Math.sqrt(5)) / 2
        const spiralAngle = index * goldenRatio * Math.PI * 2
        
        // 在父节点方向上添加小范围偏移，确保不会跑到相反方向
        const spreadAngle = Math.PI / 6 // 在父节点周围±30度范围内分布
        const phiOffset = (Math.PI / 10) * Math.sin(spiralAngle) // 使用正弦波创建球面分布
        const thetaOffset = (Math.PI * 2 / items.length) * index + (Math.PI / 4) * Math.cos(spiralAngle) // 添加余弦波变化
        
        // 计算子节点的球面坐标，确保在父节点附近
        const phi = Math.max(0.1, Math.min(Math.PI - 0.1, parentPhi + phiOffset))
        const theta = parentTheta + thetaOffset
        
        // 球面坐标转笛卡尔坐标 - 保持3D球体分布
        x = sphereRadius * Math.sin(phi) * Math.cos(theta)
        y = sphereRadius * Math.sin(phi) * Math.sin(theta)
        z = sphereRadius * Math.cos(phi)
      }
      
      // 动态选择颜色和大小
      const colorIndex = Math.min(level, colors.length - 1)
      const sizeIndex = Math.min(level, sizes.length - 1)
      nodes.push({
        id: item.id,
        name: item.name,
        category: level,
        position: new THREE.Vector3(x, y, z),
        color: colors[colorIndex],
        size: sizes[sizeIndex]
      })
      
      // 添加连接线
      if (parentId) {
        links.push({
          source: parentId,
          target: item.id,
          color: colors[colorIndex]
        })
      }
      
      // 处理子节点
      if (item.children && item.children.length > 0) {
        const currentPosition = new THREE.Vector3(x, y, z)
        const theta = Math.atan2(y, x) // 计算当前节点的角度
        processNodes(item.children, item.id, level + 1, theta, sphereRadius, currentPosition)
      }
    })
  }
  
  processNodes(categories, null, 0, 0, 150)
  
  // 节点位置优化 - 避免重叠
  optimizeNodePositions(nodes)
  
  // 自动调整相机位置以适应所有节点
  adjustCameraToFitNodes(nodes)
}

// 生成选中分类的完整图谱
const generateCategoryGraph = (category: any, nodes: any[], links: any[]) => {
  
  // 三个层级的颜色和大小
  const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7'] // 红色、青色、蓝色、绿色、黄色
  const sizes = [75, 55, 35, 28, 22] // 大、中、小、更小、最小

  // 添加当前分类作为中心节点
        nodes.push({
        id: category.id,
        name: category.name || '未命名分类',
        category: 0,
        position: new THREE.Vector3(0, 0, 50), // 中心节点稍微向前
        color: colors[0],
        size: sizes[0]
      })

  // 递归添加所有子分类（不限制层级）
  const processAllChildren = (children: any[], parentId: string, level: number, angle: number, radius: number, parentPosition?: THREE.Vector3) => {
    children.forEach((child, index) => {
      // 优化的3D球体分布算法 - 按父节点方向分布
      const sphereRadius = radius + 200 // 每层半径递增
      
      let x = 0, y = 0, z = 0
      
      if (level === 1) {
        // 第一级子节点围绕中心节点分布
        const phi = Math.acos(1 - 2 * (index + 0.5) / children.length) // 均匀分布在球面
        const theta = (2 * Math.PI / children.length) * index // 围绕中心均匀分布
        x = sphereRadius * Math.sin(phi) * Math.cos(theta)
        y = sphereRadius * Math.sin(phi) * Math.sin(theta)
        z = sphereRadius * Math.cos(phi)
      } else {
        // 获取父节点位置
        const parentPos = parentPosition || new THREE.Vector3(0, 0, 0)
        
        // 计算父节点的球面坐标
        const parentRadius = Math.sqrt(parentPos.x * parentPos.x + parentPos.y * parentPos.y + parentPos.z * parentPos.z)
        const parentPhi = Math.acos(parentPos.z / Math.max(parentRadius, 0.001))
        const parentTheta = Math.atan2(parentPos.y, parentPos.x)
        
        // 使用改进的球面均匀分布算法 - 确保从任何角度观看都有良好效果
        
        // 使用改进的球面分布算法，在父节点方向上创建均匀分布
        const goldenRatio = (1 + Math.sqrt(5)) / 2
        const spiralAngle = index * goldenRatio * Math.PI * 2
        
        // 在父节点方向上添加小范围偏移，确保不会跑到相反方向
        const spreadAngle = Math.PI / 8 // 在父节点周围±22.5度范围内分布
        const phiOffset = (Math.PI / 12) * Math.sin(spiralAngle) // 使用正弦波创建球面分布
        const thetaOffset = (Math.PI * 2 / children.length) * index + (Math.PI / 6) * Math.cos(spiralAngle) // 添加余弦波变化
        
        // 计算子节点的球面坐标，确保在父节点附近
        const phi = Math.max(0.1, Math.min(Math.PI - 0.1, parentPhi + phiOffset))
        const theta = parentTheta + thetaOffset
        
        // 球面坐标转笛卡尔坐标 - 保持3D球体分布
        x = sphereRadius * Math.sin(phi) * Math.cos(theta)
        y = sphereRadius * Math.sin(phi) * Math.sin(theta)
        z = sphereRadius * Math.cos(phi)
      }
      
      const color = colors[level] || colors[colors.length - 1]
      const size = sizes[level] || sizes[sizes.length - 1]
      
      nodes.push({
        id: child.id,
        name: child.name || '未命名子分类',
        category: level,
        position: new THREE.Vector3(x, y, z),
        color: color,
        size: size
      })
      
      // 添加连接线
      links.push({
        source: parentId,
        target: child.id,
        color: color
      })
      
      // 递归处理子节点
      if (child.children && child.children.length > 0) {
        const currentPosition = new THREE.Vector3(x, y, z)
        const theta = Math.atan2(y, x) // 计算当前节点的角度
        processAllChildren(child.children, child.id, level + 1, theta, sphereRadius, currentPosition)
      }
    })
  }
  
  if (category.children && category.children.length > 0) {
    processAllChildren(category.children, category.id, 1, 0, 120)
  }
  
  // 节点位置优化 - 避免重叠
  optimizeNodePositions(nodes)
  
  // 自动调整相机位置以适应所有节点
  adjustCameraToFitNodes(nodes)
  
}

// 递归添加子节点到图谱
const addChildrenToGraph = (children: any[], nodes: any[], links: any[], parentId: string, level: number, maxLevel?: number, parentPosition?: THREE.Vector3) => {
  // 显示所有层级的数据，不限制层级
  if (!children || children.length === 0 || level > 10) return

  children.forEach((child, index) => {
    // 优化的3D球体分布算法 - 按父节点方向分布
    const sphereRadius = 180 + level * 80 // 每层半径递增
    
    let x = 0, y = 0, z = 0
    
    if (level === 1) {
      // 第一级子节点围绕中心节点分布
      const phi = Math.acos(1 - 2 * (index + 0.5) / children.length) // 均匀分布在球面
      const theta = (2 * Math.PI / children.length) * index // 围绕中心均匀分布
      x = sphereRadius * Math.sin(phi) * Math.cos(theta)
      y = sphereRadius * Math.sin(phi) * Math.sin(theta)
      z = sphereRadius * Math.cos(phi)
    } else {
      // 获取父节点位置
      const parentPos = parentPosition || new THREE.Vector3(0, 0, 0)
      
      // 计算父节点的球面坐标
      const parentRadius = Math.sqrt(parentPos.x * parentPos.x + parentPos.y * parentPos.y + parentPos.z * parentPos.z)
      const parentPhi = Math.acos(parentPos.z / Math.max(parentRadius, 0.001))
      const parentTheta = Math.atan2(parentPos.y, parentPos.x)
      
      // 使用改进的球面均匀分布算法 - 确保从任何角度观看都有良好效果
      
      // 使用改进的球面分布算法，在父节点方向上创建均匀分布
      const goldenRatio = (1 + Math.sqrt(5)) / 2
      const spiralAngle = index * goldenRatio * Math.PI * 2
      
      // 在父节点方向上添加小范围偏移，确保不会跑到相反方向
      const spreadAngle = Math.PI / 10 // 在父节点周围±18度范围内分布
      const phiOffset = (Math.PI / 15) * Math.sin(spiralAngle) // 使用正弦波创建球面分布
      const thetaOffset = (Math.PI * 2 / children.length) * index + (Math.PI / 8) * Math.cos(spiralAngle) // 添加余弦波变化
      
      // 计算子节点的球面坐标，确保在父节点附近
      const phi = Math.max(0.1, Math.min(Math.PI - 0.1, parentPhi + phiOffset))
      const theta = parentTheta + thetaOffset
      
      // 球面坐标转笛卡尔坐标 - 保持3D球体分布
      x = sphereRadius * Math.sin(phi) * Math.cos(theta)
      y = sphereRadius * Math.sin(phi) * Math.sin(theta)
      z = sphereRadius * Math.cos(phi)
    }
    
    const position = new THREE.Vector3(x, y, z)

    const nodeData = {
      id: child.id,
      name: child.name || '未命名子分类',
      category: level,
      position: position,
      color: getColorByLevel(level + 1),
      size: getSizeByLevel(level + 1)
    }
    
    nodes.push(nodeData)

    links.push({
      source: parentId,
      target: child.id,
      color: getColorByLevel(level + 1)
    })

    // 递归处理所有子节点
    if (child.children && child.children.length > 0) {
      addChildrenToGraph(child.children, nodes, links, child.id, level + 1, maxLevel, position)
    }
  })
}

// 根据ID查找分类
const findCategoryById = (id: string): any => {
  const findInItems = (items: any[]): any => {
    for (const item of items) {
      if (item.id === id) return item
      if (item.children && item.children.length > 0) {
        const found = findInItems(item.children)
        if (found) return found
      }
    }
    return null
  }

  for (const category of knowledgeCategories.value) {
    if (category.id === id) return category
    if (category.children && category.children.length > 0) {
      const found = findInItems(category.children)
      if (found) return found
    }
  }

  return null
}

// 根据ID查找节点（右侧图谱渲染数据）
const findRightGraphNodeById = (id: string): any => {
  return rightGraphNodeMap.get(id) || null
}

// 根据entityId查找树节点
const findTreeNodeByEntityId = (entityId: string): any => {
  if (treeData.value) {
    const dfs = (node: any): any => {
      if (node.entityId === entityId) return node
      if (node.children && node.children.length > 0) {
        for (const child of node.children) {
          const found = dfs(child)
          if (found) return found
        }
      }
      return null
    }
    return dfs(treeData.value)
  }
  return null
}

// 根据entityId定位到树节点并展开路径
const locateTreeNodeByEntityId = async (entityId: string) => {
  
  if (!treeData.value) {
    return
  }
  
  // 查找目标节点
  const targetNode = findTreeNodeByEntityId(entityId)
  if (!targetNode) {
    return
  }

  
  // 收集从根节点到目标节点的路径
  const path: string[] = []
  const collectPath = (node: any, targetId: string, currentPath: string[]): boolean => {
    const nodeId = node.entityId || node.id || node.name
    currentPath.push(nodeId)
    
    if (nodeId === targetId) {
      path.push(...currentPath)
      return true
    }
    
    if (node.children && node.children.length > 0) {
      for (const child of node.children) {
        if (collectPath(child, targetId, [...currentPath])) {
          return true
        }
      }
    }
    
    return false
  }
  
  collectPath(treeData.value, entityId, [])
  
  // 展开路径上的所有节点
  expandedCategories.value = [...path]
  
  // 选中目标节点 - 使用entityId作为选中ID
  selectedKnowledge.value = entityId
  
  
  // 等待DOM更新后滚动到目标节点
  await nextTick()
  scrollToTargetNode(entityId)
  
}

// 滚动到目标节点
const scrollToTargetNode = (entityId: string) => {
  if (!knowledgeCategoriesRef.value) {
    return
  }
  
  // 等待一小段时间确保DOM完全更新
  setTimeout(() => {
    // 查找目标节点元素
    const targetElement = knowledgeCategoriesRef.value?.querySelector(`[data-entity-id="${entityId}"]`)
    
    if (targetElement) {
      
      // 滚动到目标元素
      targetElement.scrollIntoView({
        behavior: 'smooth',
        block: 'center',
        inline: 'nearest'
      })
      
      // 添加高亮效果
      targetElement.classList.add('node-highlight')
      
      // 3秒后移除高亮效果
      setTimeout(() => {
        targetElement.classList.remove('node-highlight')
      }, 3000)
      
    } else {
      // 如果找不到具体节点，至少滚动到顶部
      knowledgeCategoriesRef.value?.scrollTo({
        top: 0,
        behavior: 'smooth'
      })
    }
  }, 100)
}

// 根据ID查找左侧树节点（API tree 或默认分类）
const findLeftTreeNodeById = (id: string): any => {
  // 先在 API tree 中找（treeData 是原始树）
  if (treeData.value) {
    const dfs = (node: any): any => {
      const nodeId = node.entityId || node.id || node.name
      if (nodeId === id) return node
      if (node.children && node.children.length > 0) {
        for (const child of node.children) {
          const found = dfs(child)
          if (found) return found
        }
      }
      return null
    }
    const foundInApi = dfs(treeData.value)
    if (foundInApi) return foundInApi
  }
  // 回退到本地默认分类
  const findInItems = (items: any[]): any => {
    for (const item of items) {
      if (item.id === id) return item
      if (item.children && item.children.length > 0) {
        const found = findInItems(item.children)
        if (found) return found
      }
    }
    return null
  }
  for (const category of knowledgeCategories.value) {
    if (category.id === id) return category
    if (category.children && category.children.length > 0) {
      const found = findInItems(category.children)
      if (found) return found
    }
  }
  return null
}

// 初始化Three.js场景
const initThreeJS = () => {
  if (!chartRef.value) return

  try {
    // 清理已有资源
    if (animationId) {
      cancelAnimationFrame(animationId)
      animationId = undefined
    }
    
    // 清理3D场景
    if (scene && chartRef.value) {
      while(scene.children.length > 0) {
        const child = scene.children[0]
        scene.remove(child)
        if ((child as any).geometry) (child as any).geometry.dispose()
        if ((child as any).material) {
          if (Array.isArray((child as any).material)) {
            (child as any).material.forEach((material: any) => material.dispose())
          } else {
            (child as any).material.dispose()
          }
        }
      }
    }
    
    // 清理renderer
    if (renderer && chartRef.value) {
      renderer.dispose()
      if (chartRef.value.contains(renderer.domElement)) {
        chartRef.value.removeChild(renderer.domElement)
      }
    }
    
    // 重置所有变量
    scene = undefined
    camera = undefined
    renderer = undefined
    controls = undefined
    starNodes = []
    connectionLines = []
    starGroup = undefined
    lineGroup = undefined
    
    // 创建场景
    scene = new THREE.Scene()
    scene.background = new THREE.Color(isDarkTheme.value ? 0x000000 : 0xffffff)

    // 创建相机
    const width = chartRef.value.clientWidth
    const height = chartRef.value.clientHeight
    
    // 设置相机，确保能看到所有球体，并增强3D效果
    camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 10000) // 增加视野角度，让视角更广
    camera.position.set(500, 500, 1500) // 扩大初始视角，让节点分布更清晰

    // 创建渲染器
    renderer = new THREE.WebGLRenderer({ 
      antialias: true, 
      alpha: true,
      preserveDrawingBuffer: false,
      powerPreference: "high-performance"
    })
    renderer.setSize(width, height)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) // 限制像素比，提高性能
    renderer.shadowMap.enabled = true
    renderer.shadowMap.type = THREE.PCFSoftShadowMap
    renderer.outputColorSpace = THREE.SRGBColorSpace // 使用标准色彩空间
    renderer.toneMapping = THREE.ACESFilmicToneMapping // 使用电影级色调映射
    renderer.toneMappingExposure = 1.2 // 调整曝光
    
    // 确保canvas可以接收鼠标事件
    renderer.domElement.style.cursor = 'grab'
    renderer.domElement.style.userSelect = 'none'
    renderer.domElement.style.touchAction = 'none'
    
    chartRef.value.appendChild(renderer.domElement)

    // 创建控制器
    import('three/addons/controls/OrbitControls.js').then(({ OrbitControls }) => {
      if (!camera || !renderer) return
      
      controls = new OrbitControls(camera, renderer.domElement)
      controls.enableDamping = true
      controls.dampingFactor = 0.05
      controls.autoRotate = autoRotate.value
      controls.autoRotateSpeed = 2
      controls.enableZoom = true
      controls.enablePan = true
      controls.enableRotate = true
      controls.minDistance = 50
      controls.maxDistance = 5000 // 大幅增加最大距离
      
      // 添加事件监听器来测试控制器是否工作
      controls.addEventListener('change', () => {
        if (!camera) return
      })
    }).catch(error => {
    })

    // 创建组
    starGroup = new THREE.Group()
    lineGroup = new THREE.Group()
    if (scene && starGroup && lineGroup) {
      scene.add(starGroup)
      scene.add(lineGroup)
    }

    // 添加环境光 - 更亮的基础照明
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.8)
    if (scene) scene.add(ambientLight)

    // 添加主方向光 - 强亮光
    const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2)
    directionalLight.position.set(100, 100, 100)
    if (scene) scene.add(directionalLight)

    // 添加辅助方向光 - 更亮
    const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.6)
    directionalLight2.position.set(-100, -100, -100)
    if (scene) scene.add(directionalLight2)

    // 添加点光源增强立体感
    const pointLight = new THREE.PointLight(0xffffff, 0.8, 1000)
    pointLight.position.set(0, 0, 300)
    if (scene) scene.add(pointLight)

    // 添加粒子系统创造星空背景
    const particleCount = 1000
    const particles = new THREE.BufferGeometry()
    const particlePositions = new Float32Array(particleCount * 3)
    const particleSizes = new Float32Array(particleCount)

    for (let i = 0; i < particleCount; i++) {
      particlePositions[i * 3] = (Math.random() - 0.5) * 2000
      particlePositions[i * 3 + 1] = (Math.random() - 0.5) * 2000
      particlePositions[i * 3 + 2] = (Math.random() - 0.5) * 2000
      particleSizes[i] = Math.random() * 2 + 0.5
    }

    particles.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3))
    particles.setAttribute('size', new THREE.BufferAttribute(particleSizes, 1))

    const particleMaterial = new THREE.PointsMaterial({
      color: 0xffffff,
      size: 1,
      transparent: true,
      opacity: 0.4,
      sizeAttenuation: true
    })

    const particleSystem = new THREE.Points(particles, particleMaterial)
    if (scene) scene.add(particleSystem)

    // 初始化射线检测器和鼠标位置
    raycaster = new THREE.Raycaster()
    mouse = new THREE.Vector2()

    // 创建工具提示元素
    // 不再需要工具提示，因为节点名称直接显示在标签中

    // 添加事件监听
    if (renderer) {
      renderer.domElement.addEventListener('click', onMouseClick)
      renderer.domElement.addEventListener('mousemove', onMouseMove)
      renderer.domElement.addEventListener('mouseleave', onMouseLeave)
    }

    // 开始渲染循环
    animate()
  } catch (error) {
  }
}

// 注意：创建工具提示功能保留以备将来使用

// 鼠标点击事件处理
const onMouseClick = async (event: MouseEvent) => {
  if (!chartRef.value || !mouse || !raycaster || !camera) return

  const rect = chartRef.value.getBoundingClientRect()
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1

  raycaster.setFromCamera(mouse, camera)
  
  // 检测所有球体
  const allMeshes: THREE.Mesh[] = []
  starNodes.forEach(group => {
    group.children.forEach(child => {
      if (child instanceof THREE.Mesh) {
        allMeshes.push(child)
      }
    })
  })
  
  const intersects = raycaster.intersectObjects(allMeshes)
  
  if (intersects.length > 0) {
    const clickedMesh = intersects[0].object as THREE.Mesh
    const parentGroup = clickedMesh.parent as THREE.Group
    
    if (parentGroup && parentGroup.userData) {
      const nodeId = parentGroup.userData.id || parentGroup.userData.name
      const nodeName = parentGroup.userData.name
     
      // 查找右侧图谱节点数据，检查 point 字段
      const nodeData = findRightGraphNodeById(nodeId)
     
      // 如果节点的point字段为true，调用意识形态教育链接接口
      if (nodeData && nodeData.tag === 'point') {
    
        await handleIdeologyEducationLinks(nodeName)
      }
    
      // 添加点击动画效果
      parentGroup.scale.set(1.3, 1.3, 1.3)
      setTimeout(() => {
        parentGroup.scale.set(1, 1, 1)
      }, 300)
    }
  }
}

// 鼠标移动事件处理
const onMouseMove = (event: MouseEvent) => {
  if (!chartRef.value || !mouse || !raycaster || !camera) return

  const rect = chartRef.value.getBoundingClientRect()
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1

  raycaster.setFromCamera(mouse, camera)
  // 获取所有球形节点中的Mesh对象
  const allMeshes: THREE.Mesh[] = []
  starNodes.forEach(group => {
    group.children.forEach(child => {
      if (child instanceof THREE.Mesh) {
        allMeshes.push(child)
      }
    })
  })
  // 也检测连接线
  const allObjects = [...allMeshes, ...connectionLines]
  const intersects = raycaster.intersectObjects(allObjects)
  
  if (intersects.length > 0) {
    const intersectedObject = intersects[0].object
    
    if (intersectedObject instanceof THREE.Mesh) {
      // 悬浮在球体上
      const mesh = intersectedObject as THREE.Mesh
      // 找到对应的Group
      let sphereGroup: THREE.Group | null = null
      for (const group of starNodes) {
        if (group.children.includes(mesh)) {
          sphereGroup = group
          break
        }
      }
      
      if (!sphereGroup || !sphereGroup.userData || !sphereGroup.userData.id || !sphereGroup.userData.name) {
     
        return
      }
      
 
      
      if (hoveredSphere !== sphereGroup) {
        // 重置之前悬浮的球形节点
        if (hoveredSphere) {

          resetSphereHighlight(hoveredSphere)
        }
        
        // 高亮当前悬浮的球形节点
        hoveredSphere = sphereGroup
  
        highlightSphere(sphereGroup)
      }
    } else if (intersectedObject instanceof THREE.Line) {
      // 悬浮在连接线上
      const line = intersectedObject as THREE.Line
      
      // 重置球体高亮
      if (hoveredSphere) {
        resetSphereHighlight(hoveredSphere)
        hoveredSphere = null
      }
      
      // 高亮单条连接线
      resetAllLineHighlights()
      highlightSingleLine(line)
      }
  } else {
    // 鼠标离开所有对象
    if (hoveredSphere) {
      resetSphereHighlight(hoveredSphere)
      hoveredSphere = null
    }
    resetAllLineHighlights()
  }
}

// 鼠标离开事件处理
const onMouseLeave = () => {
  if (hoveredSphere) {
    resetSphereHighlight(hoveredSphere)
    hoveredSphere = null
  }
}

// 强制重置所有高亮状态
const resetAllHighlights = () => {
  resetCount++
  isResetting = true // 设置重置状态
  
  // 重置所有球形节点
  starNodes.forEach((sphereGroup, index) => {
    sphereGroup.children.forEach(child => {
      if (child instanceof THREE.Mesh && child.material) {
        const material = child.material as THREE.MeshPhongMaterial
    material.opacity = 0.8
        const color = sphereGroup.userData.originalColor || sphereGroup.userData.color
    if (color && typeof color === 'string') {
      const hexColor = parseInt(color.replace('#', '0x'))
      material.color.setHex(hexColor)
         
      
      // 立即检查颜色是否设置成功
      const actualColor = material.color.getHexString()
        
    } else {
  
    }
      }
    })
    sphereGroup.scale.set(1, 1, 1)
  })
  
  // 重置所有连接线
  connectionLines.forEach((line, index) => {
    const lineMaterial = line.material as THREE.LineBasicMaterial
    lineMaterial.opacity = 0.6
    lineMaterial.linewidth = 3
    const originalColor = line.userData.originalColor
    if (originalColor && typeof originalColor === 'string') {
      const hexColor = parseInt(originalColor.replace('#', '0x'))
      lineMaterial.color.setHex(hexColor)

    }
  })
  
  // 重置所有标签
  starNodes.forEach((sphereGroup, index) => {
    sphereGroup.children.forEach(child => {
      if (child instanceof THREE.Group) {
        child.children.forEach(spriteChild => {
          if (spriteChild instanceof THREE.Sprite) {
            const spriteMaterial = spriteChild.material as THREE.SpriteMaterial
            spriteMaterial.opacity = 1.0
            const originalScale = spriteChild.userData.originalScale || { x: 1, y: 1, z: 1 }
            spriteChild.scale.set(originalScale.x, originalScale.y, originalScale.z)
            
            // 移除发光效果
            if (spriteChild.userData.glowMesh) {
              child.remove(spriteChild.userData.glowMesh)
              spriteChild.userData.glowMesh = null
            }
          
          }
        })
      }
    })
  })
  
  // 重置悬浮状态
  hoveredSphere = null
  hideTooltip()
  
  // 强制渲染一帧，确保重置效果立即显示
  if (renderer && scene && camera) {
    renderer.render(scene, camera)
  }
  
  // 延迟重置标志，确保动画循环有足够时间处理
  setTimeout(() => {
    isResetting = false
    
    // 检查重置是否真的生效
    starNodes.forEach((star, index) => {
      // 找到球体网格
      const sphereMesh = star.children.find(child => child instanceof THREE.Mesh) as THREE.Mesh
      if (sphereMesh && sphereMesh.material) {
        const material = sphereMesh.material as THREE.MeshBasicMaterial
      const currentColor = material.color.getHexString()
      const expectedColor = star.userData.color ? star.userData.color.replace('#', '') : 'unknown'
      }
    })
  }, 2000) // 增加到2000ms，确保有足够时间
}

// 高亮球形节点及其相关连接线
const highlightSphere = (sphere: THREE.Group) => {
  // 高亮球形节点本身
  sphere.children.forEach(child => {
    if (child instanceof THREE.Mesh && child.material) {
      const material = child.material as THREE.MeshPhongMaterial
  material.opacity = 1
  material.color.setHex(0xffffff)
    }
  })
  
  // 增大球形节点尺寸
  sphere.scale.set(1.3, 1.3, 1.3)
  
  // 高亮相关连接线
  const nodeId = sphere.userData.id || sphere.userData.name
  
  connectionLines.forEach((line, index) => {
    const lineMaterial = line.material as THREE.LineBasicMaterial
    if (isLineRelatedToNode(line, nodeId)) {
      lineMaterial.opacity = 1.0
      // 根据主题设置高亮颜色
      const highlightColor = isDarkTheme.value ? 0xffffff : 0x1a1a1a
      lineMaterial.color.setHex(highlightColor)
      lineMaterial.linewidth = 4
    } else {
      // 降低不相关连接线的透明度
      lineMaterial.opacity = 0.2
      lineMaterial.color.setHex(0x666666) // 灰色
      lineMaterial.linewidth = 1
    }
  })
  
  // 高亮对应的标签
  sphere.children.forEach(child => {
    if (child instanceof THREE.Group) {
      child.children.forEach(spriteChild => {
        if (spriteChild instanceof THREE.Sprite) {
          const spriteMaterial = spriteChild.material as THREE.SpriteMaterial
          spriteMaterial.opacity = 1
          // 增大标签尺寸，让悬停效果更明显
          spriteChild.scale.multiplyScalar(1.8)
          
          // 添加发光效果
          if (!spriteChild.userData.glowMesh) {
            const glowGeometry = new THREE.PlaneGeometry(1, 1)
            const glowMaterial = new THREE.MeshBasicMaterial({
              color: 0xffffff,
              transparent: true,
              opacity: 0.3,
              side: THREE.DoubleSide
            })
            const glowMesh = new THREE.Mesh(glowGeometry, glowMaterial)
            glowMesh.scale.copy(spriteChild.scale)
            glowMesh.scale.multiplyScalar(1.2)
            glowMesh.position.copy(spriteChild.position)
            glowMesh.position.z = -0.1
            child.add(glowMesh)
            spriteChild.userData.glowMesh = glowMesh
          }
        }
      })
    }
  })

}

// 重置球形节点高亮
const resetSphereHighlight = (sphere: THREE.Group) => {
  try {
    
    // 重置球形节点本身
    sphere.children.forEach(child => {
      if (child instanceof THREE.Mesh && child.material) {
        const material = child.material as THREE.MeshPhongMaterial
    material.opacity = 0.8
    
        // 安全地处理颜色 - 从userData中获取
        const color = sphere.userData.originalColor || sphere.userData.color
    if (color && typeof color === 'string') {
      const hexColor = parseInt(color.replace('#', '0x'))
              material.color.setHex(hexColor)
        
        
        // 立即检查颜色是否设置成功
        const actualColor = material.color.getHexString()
        
      } else {
         
        }
      }
    })
    
    // 重置球形节点尺寸
    sphere.scale.set(1, 1, 1)
    
    // 重置所有连接线
    connectionLines.forEach((line, index) => {
      const lineMaterial = line.material as THREE.LineBasicMaterial
      lineMaterial.opacity = 0.8
      lineMaterial.linewidth = 3
      // 恢复原来的颜色
      lineMaterial.color.setHex(line.userData.originalColor || 0x666666)
    })
    
    // 重置对应的标签
    sphere.children.forEach(child => {
      if (child instanceof THREE.Group) {
        child.children.forEach(spriteChild => {
          if (spriteChild instanceof THREE.Sprite) {
            const spriteMaterial = spriteChild.material as THREE.SpriteMaterial
            spriteMaterial.opacity = 1.0
            // 重置标签尺寸
            const originalScale = spriteChild.userData.originalScale || { x: 1, y: 1, z: 1 }
            spriteChild.scale.set(originalScale.x, originalScale.y, originalScale.z)
            
            // 移除发光效果
            if (spriteChild.userData.glowMesh) {
              child.remove(spriteChild.userData.glowMesh)
              spriteChild.userData.glowMesh = null
            }
          }
        })
      }
    })
    
    // 强制渲染一帧，确保重置效果立即显示
    if (renderer && scene && camera) {
      renderer.render(scene, camera)
    }
    
    // 延迟一下，确保重置生效
    setTimeout(() => {
     
    }, 50)
  } catch (error) {
  
  }
}

// 高亮单条连接线
const highlightSingleLine = (line: THREE.Line) => {
  if (line.material) {
    const material = line.material as THREE.LineBasicMaterial
    // 根据主题设置高亮颜色
    const highlightColor = isDarkTheme.value ? 0xffffff : 0x1a1a1a // 星空主题用白色，白色主题用黑色
    material.color.setHex(highlightColor)
    material.opacity = 1.0
    material.linewidth = 5 // 悬浮时变粗
  }
}

// 重置所有连接线高亮
const resetAllLineHighlights = () => {
  connectionLines.forEach(line => {
    if (line.material && line.userData) {
      const material = line.material as THREE.LineBasicMaterial
      material.color.setHex(line.userData.originalColor || 0x666666)
      material.opacity = 0.9 // 更不透明
      material.linewidth = 3 // 增加默认粗细
    }
  })
}

// 检查连接线是否与节点相关
const isLineRelatedToNode = (line: THREE.Line, nodeId: string): boolean => {
  const sourceId = line.userData.sourceId
  const targetId = line.userData.targetId
  return sourceId === nodeId || targetId === nodeId
}

// 注意：工具提示功能保留以备将来使用

// 隐藏工具提示
const hideTooltip = () => {
  // 保留以备将来使用
}

// 更新3D图谱
const update3DGraph = () => {
  try {
    // 重置悬浮状态
    resetAllHighlights()
    
    // 清除现有对象
    if (starGroup) starGroup.clear()
    if (lineGroup) lineGroup.clear()
    starNodes = []
    connectionLines = []

    const graphDataResult = generate3DGraphData()
    
    const nodeMap = new Map()
    rightGraphNodeMap.clear()

    // 创建星星节点
    graphDataResult.nodes.forEach((node: any, index: number) => {
      
      if (node && node.position && node.color && node.size !== undefined) {
        // 确保 category 是数字类型
        const level = typeof node.category === 'number' ? node.category : 0
        const nodeName = node.name || '未命名节点'   
        const sphere = createSphereNode(node.position, node.color, node.size, level, nodeName)
        
        // 设置 userData - 确保所有属性都有值
        const nodeId = node.id || `node_${Date.now()}_${Math.random()}`
        const nodeColor = node.color || '#FFD700'
        
        sphere.userData = {
          id: nodeId,
          name: nodeName,
          color: nodeColor,
          category: level
        }
        
        // 确保球体位置正确设置
        sphere.position.copy(node.position)
      
        if (starGroup) starGroup.add(sphere)
        starNodes.push(sphere)
        nodeMap.set(nodeId, sphere) // 存储球体节点，而不是原始数据节点
        rightGraphNodeMap.set(nodeId, node) // 记录右侧图谱的节点原始数据（含 point 等）
        
      } else {
      }
    })
    
    graphDataResult.links.forEach((link: any, index: number) => {
      const sourceNode = nodeMap.get(link.source)
      const targetNode = nodeMap.get(link.target)
      if (sourceNode && targetNode) {
        // 创建连接线，不穿过球体内部
        const sourcePos = sourceNode.position.clone()
        const targetPos = targetNode.position.clone()
        
        // 计算方向向量
        const direction = targetPos.clone().sub(sourcePos).normalize()
        
        // 获取球体大小（假设都是相似大小）
        const sourceSize = 25 // 球体半径
        const targetSize = 25
        
        // 计算起点和终点（球体表面）
        const startPoint = sourcePos.clone().add(direction.clone().multiplyScalar(sourceSize))
        const endPoint = targetPos.clone().sub(direction.clone().multiplyScalar(targetSize))
        
        // 创建普通连接线
        const points = [startPoint, endPoint]
        const geometry = new THREE.BufferGeometry().setFromPoints(points)
        const material = new THREE.LineBasicMaterial({ 
          color: isDarkTheme.value ? 0xffffff : 0x666666,
          linewidth: 3, // 增加粗细，更容易看见
          transparent: true,
          opacity: 0.9 // 更不透明
        })
        const line = new THREE.Line(geometry, material)
        
        // 存储原始材质属性用于悬浮效果
        line.userData = {
          originalColor: isDarkTheme.value ? 0xffffff : 0x666666,
          originalWidth: 3,
          hoveredWidth: 5,
          sourceId: link.source,
          targetId: link.target
        }
        
        // 设置渲染顺序，确保连接线可见
        line.renderOrder = 1
        
        if (lineGroup) lineGroup.add(line)
        connectionLines.push(line)
      } else {
      }
    })
    
    // 确保动画正在运行
    if (!animationId && is3DMode.value) {
      animate()
    }
    
  } catch (error) {
  }
}

// 动画循环
const animate = () => {
  // 检查是否应该继续动画
  if (!is3DMode.value || !scene || !camera || !renderer) {
    return
  }
  
  animationId = requestAnimationFrame(animate)

  try {
    const time = Date.now() * 0.001

    // 球形节点动画 - 完全停止旋转，只保留脉动效果
    starNodes.forEach((sphereGroup) => {
      if (sphereGroup) {
        // 完全停止球体旋转，避免任何抖动
        // sphereGroup.rotation.z += 0.0001  // 注释掉旋转
        // sphereGroup.rotation.y += 0.00005  // 注释掉旋转
        
        // 保留简单的脉动效果，但减少幅度避免抖动
        if (hoveredSphere !== sphereGroup) {
          const randomPhase = Math.random() * 0.5
          const scale = 1 + 0.02 * Math.sin(time * 0.5 + randomPhase) // 减少脉动幅度和频率
          sphereGroup.scale.set(scale, scale, scale)
        } else {
          // 悬浮状态
          sphereGroup.scale.set(1.2, 1.2, 1.2)
        }
      }
    })

    // 标签动画 - 完全禁用抖动，使用固定朝向
    starNodes.forEach((sphereGroup) => {
      if (sphereGroup && camera) {
        // 找到标签（sprite）
        const label = sphereGroup.children.find(child => child instanceof THREE.Sprite)
        if (label && label instanceof THREE.Sprite) {
          // 完全禁用标签的自动朝向，避免抖动
          // 标签将保持固定朝向，不会跟随摄像机旋转
          // 这样可以完全消除字体抖动问题
        }
      }
    })

    // 连接线动画
    connectionLines.forEach((line) => {
      if (line && line.material) {
        const material = line.material as THREE.LineBasicMaterial
        
        // 在重置状态下，不改变连接线状态
        if (isResetting) {
          return
        }
        
        // 颜色变化 - 只在非悬浮状态下进行
        if (!hoveredSphere) {
          const randomOffset = Math.random() * 0.3
          const opacity = 0.4 + 0.4 * Math.sin(time + randomOffset)
          material.opacity = opacity
          
          const originalColor = line.userData.originalColor
          if (originalColor && typeof originalColor === 'string') {
            try {
              const color = new THREE.Color(originalColor)
              const pulseColor = new THREE.Color().setHSL(
                color.getHSL({ h: 0, s: 0, l: 0 }).h,
                color.getHSL({ h: 0, s: 0, l: 0 }).s,
                0.6 + 0.2 * Math.sin(time * 1.5 + Math.random() * 0.2)
              )
              material.color.copy(pulseColor)
            } catch (colorError) {
            }
          }
        } else {
          // 悬浮状态下，根据是否相关设置连接线状态
          const nodeId = hoveredSphere.userData.id
          if (isLineRelatedToNode(line, nodeId)) {
            material.opacity = 1
            // 根据主题设置高亮颜色
            const highlightColor = isDarkTheme.value ? 0xffffff : 0x1a1a1a
            material.color.setHex(highlightColor)
            material.linewidth = 4
          } else {
            material.opacity = 0.1
          }
        }
      }
    })

    // 更新控制器
    if (controls) {
      try {
        controls.update()
      } catch (error) {
      }
    } else {
    }

    if (renderer && scene && camera) {
      renderer.render(scene, camera)
    }
  } catch (error) {
  }
}

// 获取知识点描述
const getKnowledgeDescription = (): string => {
  // 这里可以根据当前节点名称返回相应的描述
  // 暂时返回一个通用描述，实际项目中可以从API获取或维护一个描述映射
  return `${currentNodeName.value}相关的技术规范和标准值，用于指导实际工程应用。`
}

// 处理意识形态教育链接
const handleIdeologyEducationLinks = async (nodeName: string) => {
  try {
    drawerLoading.value = true
    drawerVisible.value = true
    currentNodeName.value = nodeName
    const response = await getIdeologyEducationLinks(nodeName)

    // 从图谱节点数据中获取describe字段
    let nodeDescribe = ''

    if (graphData.value && graphData.value.children) {
      const node = graphData.value.children.find((n: any) => n.name === nodeName)
      if (node && node.describe) {
        nodeDescribe = node.describe
      }
    }
    
    // 将describe字段添加到响应数据中
    drawerData.value = {
      list: response,
      describe: nodeDescribe
    }
  } catch (error) {
    drawerData.value = { error: '获取数据失败' }
  } finally {
    drawerLoading.value = false
  }
}

// 根据entityId处理节点选择（用于搜索结果）
const handleNodeSelectByEntityId = async (entityId: string) => {
  
  // 清除聚焦状态，显示完整的图谱
  focusedNode.value = ''

  
  try {
    // 调用根据实体ID获取知识图谱的接口
    const response = await getGraphByEntityId(entityId)

    
    let graphDataResult = null
    let treeDataResult = null
    
    // 处理不同的响应格式
    if (response && response.data) {
      if (response.data.graph && response.data.tree) {
        // 标准格式: { data: { graph: {...}, tree: {...} } }
        graphDataResult = response.data.graph
        treeDataResult = response.data.tree
      } else if (response.data.graph) {
        // 只有graph数据
        graphDataResult = response.data.graph
        treeDataResult = response.data.tree || treeData.value
      } else {
        // 直接是图谱数据
        graphDataResult = response.data
        treeDataResult = treeData.value
      }
    } else if (response && response.graph && response.tree) {
      // 直接返回 { graph: {...}, tree: {...} }
      graphDataResult = response.graph
      treeDataResult = response.tree
    } else if (response && response.entityId) {
      // 直接返回节点对象，需要保持原有的树结构
      
      graphDataResult = response
      treeDataResult = treeData.value // 保持原有的树结构
    } else {
     
      return
    }
    
    
    // 在树数据中查找对应的节点并选中
    const nodeData = findTreeNodeByEntityId(entityId)
    if (nodeData) {
      selectedKnowledge.value = nodeData.entityId
    }
    
    // 更新图谱数据
    graphData.value = graphDataResult
    treeData.value = treeDataResult
    
    // 重新渲染图谱
    await nextTick()
    if (is3DMode.value) {
      await initThreeJS()
      update3DGraph() // 确保在初始化后更新图谱数据
    } else {
      await initVisNetwork()
      updateVisNetwork() // 确保在初始化后更新图谱数据
    }
  } catch (error) {
  }
}

// 处理节点选择
const handleNodeSelect = async (nodeId: string) => {
  
  // 清除聚焦状态，显示完整的图谱
  focusedNode.value = ''

  
  // 单选逻辑：直接选中点击的节点
  selectedKnowledge.value = nodeId

  
  // 查找左侧树节点数据，获取 entityId
  const nodeData = findLeftTreeNodeById(nodeId)

  
  try {
    // 如果节点有entityId，调用getGraphByEntityId接口
    if (nodeData && nodeData.clickFlag===true) {
     
      await fetchGraphDataByEntityId(nodeData.entityId)
    } else {
    
      await fetchGraphData()
    }
  } catch (error) {
   
    // 如果API调用失败，回退到原来的逻辑
    if (is3DMode.value) {
      update3DGraph()
    } else {
      updateVisNetwork()
    }
  }
}

// 处理节点聚焦（显示该节点及其子节点）
const handleNodeFocus = (nodeId: string) => {

  
  // 如果当前聚焦的就是这个节点，则取消聚焦
  if (focusedNode.value === nodeId) {
    focusedNode.value = ''
   
  } else {
    // 否则聚焦这个节点
    focusedNode.value = nodeId

  }
  
  // 更新图谱
  if (is3DMode.value) {
    update3DGraph()
  } else {
    updateVisNetwork()
  }
}

// 清除节点聚焦
const clearNodeFocus = () => {
  if (focusedNode.value) {
    focusedNode.value = ''
    
    // 更新图谱
    if (is3DMode.value) {
      update3DGraph()
    } else {
      updateVisNetwork()
    }
  }
}

// 获取知识图谱数据
const fetchGraphData = async () => {
  try {
    isLoading.value = true
    apiError.value = null

    
    const response = await getGraph()

    
    let graphDataResult = null
    let treeDataResult = null
    
    // 处理不同的响应格式
    if (response && response.data) {
      if (response.data.graph && response.data.tree) {
        // 标准格式: { data: { graph: {...}, tree: {...} } }
        graphDataResult = response.data.graph
        treeDataResult = response.data.tree
      } else if (response.data.graph) {
        // 只有graph数据
        graphDataResult = response.data.graph
        treeDataResult = response.data.tree || null
      } else {
        // 直接是图谱数据
        graphDataResult = response.data
        treeDataResult = null
      }
    } else if (response && response.graph && response.tree) {
      // 直接返回 { graph: {...}, tree: {...} }
      graphDataResult = response.graph
      treeDataResult = response.tree
    } else if (response && response.entityId) {
      // 直接返回节点对象
      graphDataResult = response
      treeDataResult = null
    } else {
      return
    }
    
    // 更新图谱数据
    graphData.value = graphDataResult
    treeData.value = treeDataResult
    
    
    // 重新渲染图谱
    await nextTick()
    if (is3DMode.value) {
      // 如果3D场景已初始化，只更新图谱数据
      if (scene && camera && renderer) {
        update3DGraph()
      } else {
        await initThreeJS()
        update3DGraph() // 确保在初始化后更新图谱数据
      }
    } else {
      // 如果2D网络已初始化，只更新数据
      if (visNetwork) {
        updateVisNetwork()
      } else {
        await initVisNetwork()
        updateVisNetwork() // 确保在初始化后更新图谱数据
      }
    }
  } catch (error) {

    apiError.value = error instanceof Error ? error.message : '获取数据失败'
  } finally {
    isLoading.value = false
  }
}

// 根据实体ID获取知识图谱数据
const fetchGraphDataByEntityId = async (entityId: string) => {
  try {
    isLoading.value = true
    apiError.value = null
    
    const response = await getGraphByEntityId(entityId)

    let graphDataResult = null
    let treeDataResult = null
    
    // 处理不同的响应格式
    if (response && response.data) {
      if (response.data.graph && response.data.tree) {
        // 标准格式: { data: { graph: {...}, tree: {...} } }
        graphDataResult = response.data.graph
        treeDataResult = response.data.tree
      } else if (response.data.graph) {
        // 只有graph数据
        graphDataResult = response.data.graph
        treeDataResult = response.data.tree || treeData.value
      } else {
        // 直接是图谱数据
        graphDataResult = response.data
        treeDataResult = treeData.value
      }
    } else if (response && response.graph && response.tree) {
      // 直接返回 { graph: {...}, tree: {...} }
      graphDataResult = response.graph
      treeDataResult = response.tree
    } else if (response && response.entityId) {
      // 直接返回节点对象，需要保持原有的树结构
      
      graphDataResult = response
      treeDataResult = treeData.value // 保持原有的树结构
    } else {

      return
    }
    
    
    // 更新图谱数据
    graphData.value = graphDataResult
    treeData.value = treeDataResult
    

    // 重新渲染图谱 - 只更新数据，不重新初始化场景
    await nextTick()
    if (is3DMode.value) {
      // 如果3D场景已初始化，只更新图谱数据
      if (scene && camera && renderer) {
        update3DGraph()
      } else {
        await initThreeJS()
        update3DGraph() // 确保在初始化后更新图谱数据
      }
    } else {
      // 如果2D网络已初始化，只更新数据
      if (visNetwork) {
        updateVisNetwork()
      } else {
        await initVisNetwork()
        updateVisNetwork() // 确保在初始化后更新图谱数据
      }
    }
  } catch (error) {
    apiError.value = error instanceof Error ? error.message : '获取数据失败'
  } finally {
    isLoading.value = false
  }
}

// 初始化展开所有节点
const initExpandedCategories = () => {
  const expandAll = (categories: KnowledgeCategory[]) => {
    categories.forEach(category => {
      expandedCategories.value.push(category.id)
      if (category.children) {
        expandAllItems(category.children)
      }
    })
  }
  
  const expandAllItems = (items: KnowledgeItem[]) => {
    items.forEach(item => {
      expandedCategories.value.push(item.id)
      if (item.children) {
        expandAllItems(item.children)
      }
    })
  }
  
  expandedCategories.value = []
  expandAll(knowledgeCategories.value)
  
  // 确保所有节点都被展开

}

// 处理节点展开/收起
const handleNodeToggle = (nodeId: string) => {
  const index = expandedCategories.value.indexOf(nodeId)
  if (index > -1) {
    expandedCategories.value.splice(index, 1)
  } else {
    expandedCategories.value.push(nodeId)
  }
}

// 重置相机
const resetCamera = () => {
  if (controls && camera) {
    controls.reset()
    // 手动设置到合适的视角 - 让重置视角更远一些
    camera.position.set(500, 500, 1500)
    camera.lookAt(0, 0, 0)
    controls.update()
  }
}

// 切换自动旋转
const toggleAutoRotate = () => {
  autoRotate.value = !autoRotate.value
  if (controls) {
    controls.autoRotate = autoRotate.value
  }
}

// 切换全屏
const toggleFullscreen = () => {
  isFullscreen.value = !isFullscreen.value
  
  // 延迟一点时间让DOM更新，然后重新调整画布大小
  nextTick(() => {
    if (is3DMode.value) {
      // 3D模式下重新调整渲染器大小
      if (chartRef.value && renderer && camera) {
        const width = chartRef.value.clientWidth
        const height = chartRef.value.clientHeight
        
        renderer.setSize(width, height)
        camera.aspect = width / height
        camera.updateProjectionMatrix()
      }
    } else {
      // 2D模式下重新调整vis-network大小
      if (visNetwork) {
        visNetwork.redraw()
        visNetwork.fit({
          animation: {
            duration: 300,
            easingFunction: 'easeInOutQuad'
          }
        })
      }
    }
  })
}

// 开始拖拽调整面板宽度
const startResize = (e: MouseEvent | TouchEvent) => {
  e.preventDefault()
  isResizing.value = true
  
  const startX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX
  const startWidth = panelWidth.value
  
  const handleMouseMove = (e: MouseEvent | TouchEvent) => {
    if (!isResizing.value) return
    
    const currentX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX
    const deltaX = currentX - startX
    const newWidth = startWidth + deltaX
    
    // 限制最小和最大宽度
    const minWidth = 200
    const maxWidth = 600
    
    if (newWidth >= minWidth && newWidth <= maxWidth) {
      panelWidth.value = newWidth
      // 更新CSS变量，用于全屏模式下的宽度计算
      document.documentElement.style.setProperty('--panel-width', newWidth + 'px')
      
      // 强制重新计算右侧区域布局
      nextTick(() => {
        if (chartRef.value && renderer && camera) {
          const width = chartRef.value.clientWidth
          const height = chartRef.value.clientHeight
          renderer.setSize(width, height)
          camera.aspect = width / height
          camera.updateProjectionMatrix()
        }
      })
    }
  }
  
  const handleMouseUp = () => {
    isResizing.value = false
    document.removeEventListener('mousemove', handleMouseMove)
    document.removeEventListener('mouseup', handleMouseUp)
    document.removeEventListener('touchmove', handleMouseMove)
    document.removeEventListener('touchend', handleMouseUp)
  }
  
  document.addEventListener('mousemove', handleMouseMove)
  document.addEventListener('mouseup', handleMouseUp)
  document.addEventListener('touchmove', handleMouseMove)
  document.addEventListener('touchend', handleMouseUp)
}

// 切换面板
const togglePanel = () => {
  panelCollapsed.value = !panelCollapsed.value
  console.log('Panel collapsed:', panelCollapsed.value)
  
  nextTick(() => {
    // 等待DOM更新完成
    setTimeout(() => {
      // 强制重新计算布局
      if (chartRef.value) {
        const width = chartRef.value.clientWidth
        const height = chartRef.value.clientHeight
        console.log('Panel toggled, new dimensions:', width, height)
        
        if (renderer && camera) {
          renderer.setSize(width, height)
          camera.aspect = width / height
          camera.updateProjectionMatrix()
        }
        // 强制重置所有高亮状态，确保布局变化后状态正确
        resetAllHighlights()
      }
    }, 150)
  })
}

// 切换主题
const toggleTheme = () => {
  isDarkTheme.value = !isDarkTheme.value
  console.log('Theme switched to:', isDarkTheme.value ? 'Dark (星空)' : 'Light (白色)')
  
  // 应用主题
  applyTheme()
}

// 应用主题
const applyTheme = () => {
  if (scene) {
    // 设置3D场景背景色
    scene.background = new THREE.Color(isDarkTheme.value ? 0x000000 : 0xffffff)
  }
  
  // 更新2D图谱背景色
  update2DTheme()
  
  // 更新连接线颜色
  updateEdgeColors()
  
  // 更新加载状态样式
  updateLoadingStyles()
}

// 更新2D图谱主题
const update2DTheme = () => {
  if (visContainer.value) {
    visContainer.value.style.backgroundColor = isDarkTheme.value ? '#000000' : '#ffffff'
  }
  
  // 如果2D图谱已初始化，重新设置背景
  if (visNetwork) {
    const options = {
      background: {
        color: isDarkTheme.value ? '#000000' : '#ffffff',
        image: null
      }
    }
    visNetwork.setOptions(options)
  }
}

// 更新连接线颜色
const updateEdgeColors = () => {
  if (!connectionLines) return
  
  connectionLines.forEach(line => {
    if (line.material && line.userData) {
      const material = line.material as THREE.LineBasicMaterial
      const defaultColor = isDarkTheme.value ? 0xffffff : 0x666666
      material.color.setHex(line.userData.originalColor || defaultColor)
    }
  })
}

// 更新加载状态样式
const updateLoadingStyles = () => {
  // 这里可以添加动态更新加载状态样式的逻辑
  // 由于样式是通过CSS类控制的，我们通过添加/移除类来实现
  const loadingContainer = document.querySelector('.graph-loading-container')
  if (loadingContainer) {
    if (isDarkTheme.value) {
      loadingContainer.classList.add('dark-theme')
      loadingContainer.classList.remove('light-theme')
    } else {
      loadingContainer.classList.add('light-theme')
      loadingContainer.classList.remove('dark-theme')
    }
  }
}

// 响应式处理
const handleResize = () => {
  if (renderer && chartRef.value && camera) {
    renderer.setSize(chartRef.value.clientWidth, chartRef.value.clientHeight)
    camera.aspect = chartRef.value.clientWidth / chartRef.value.clientHeight
    camera.updateProjectionMatrix()
  }
}

// ==================== Vis-Network 2D图谱相关函数 ====================

// 改进的分层径向布局算法 - 一级节点居中，其他节点按层级散开
const calculateHierarchicalLayout = (nodes: any[], edges: any[]) => {
  if (!nodes || nodes.length === 0) return []
  
  const nodeMap = new Map(nodes.map(node => [node.id, { ...node, level: 0, visited: false, children: [] }]))
  
  // 计算每个节点的层级和子节点关系
  const calculateLevels = () => {
    const visited = new Set()
    const queue: any[] = []
    
    // 找到所有根节点（没有入边的节点）
    const rootNodes = nodes.filter(node => 
      !edges.some(edge => edge.to === node.id)
    )
    
    // 如果没有明显的根节点，选择连接最多的节点作为根节点
    if (rootNodes.length === 0 && nodes.length > 0) {
      const nodeConnections = new Map()
      nodes.forEach(node => {
        const connections = edges.filter(edge => 
          edge.from === node.id || edge.to === node.id
        ).length
        nodeConnections.set(node.id, connections)
      })
      
      const maxConnections = Math.max(...Array.from(nodeConnections.values()))
      const centralNode = nodes.find(node => 
        nodeConnections.get(node.id) === maxConnections
      )
      if (centralNode) rootNodes.push(centralNode)
    }
    
    // 从根节点开始BFS计算层级
    rootNodes.forEach(rootNode => {
      const node = nodeMap.get(rootNode.id)
      if (node) {
        node.level = 0
        queue.push(node)
        visited.add(node.id)
      }
    })
    
    while (queue.length > 0) {
      const currentNode = queue.shift()!
      
      // 找到所有子节点
      const children = edges
        .filter(edge => edge.from === currentNode.id)
        .map(edge => nodeMap.get(edge.to))
        .filter(child => child && !visited.has(child.id))
      
      children.forEach(child => {
        child.level = currentNode.level + 1
        child.parent = currentNode
        currentNode.children.push(child)
        queue.push(child)
        visited.add(child.id)
      })
    }
    
    // 处理孤立节点
    nodeMap.forEach(node => {
      if (!visited.has(node.id)) {
        node.level = 1 // 孤立节点放在第一层
      }
    })
  }
  
  calculateLevels()
  
  // 按层级分组节点
  const levelGroups: { [key: number]: any[] } = {}
  nodeMap.forEach(node => {
    if (!levelGroups[node.level]) {
      levelGroups[node.level] = []
    }
    levelGroups[node.level].push(node)
  })
  
  const maxLevel = Math.max(...Object.keys(levelGroups).map(k => parseInt(k)))
  
  // 径向分层布局
  Object.keys(levelGroups).forEach(levelStr => {
    const level = parseInt(levelStr)
    const levelNodes = levelGroups[level]
    
    if (level === 0) {
      // 一级节点（根节点）放在中心位置
    levelNodes.forEach((node, index) => {
        if (levelNodes.length === 1) {
          node.x = 0
          node.y = 0
        } else {
          // 如果有多个根节点，在中心区域小范围分布
          const angle = (index / levelNodes.length) * 2 * Math.PI
          const radius = 50 // 很小的半径
          node.x = Math.cos(angle) * radius
          node.y = Math.sin(angle) * radius
        }
      })
    } else {
      // 其他层级节点径向分布
      const baseRadius = 150 + (level - 1) * 120 // 基础半径，每层递增
      const angleStep = (2 * Math.PI) / Math.max(levelNodes.length, 3) // 确保最小角度间隔
      
      levelNodes.forEach((node, index) => {
        // 计算该节点在当前层的角度
        let angle = index * angleStep
        
        // 如果节点有父节点，尝试让它靠近父节点的角度方向
        if (node.parent && node.parent.x !== undefined && node.parent.y !== undefined) {
          const parentAngle = Math.atan2(node.parent.y, node.parent.x)
          const parentRadius = Math.sqrt(node.parent.x * node.parent.x + node.parent.y * node.parent.y)
          
          // 如果父节点不在中心，则让子节点朝父节点的方向偏移
          if (parentRadius > 10) {
            angle = parentAngle + (index - levelNodes.length / 2) * angleStep * 0.3
          }
        }
        
        // 添加一些随机偏移避免完全对称（可选）
        const randomOffset = (Math.random() - 0.5) * 0.2 // 小幅随机偏移
        angle += randomOffset
        
        // 计算最终位置
        const radius = baseRadius + (Math.random() - 0.5) * 20 // 添加小幅径向随机偏移
        node.x = Math.cos(angle) * radius
        node.y = Math.sin(angle) * radius
      })
    }
  })
  
  // 优化连接线长度和避免交叉
  optimizeLayout(nodeMap, edges, levelGroups)
  
  return Array.from(nodeMap.values())
}

// 优化布局以减少连接线交叉和优化长度
const optimizeLayout = (nodeMap: Map<string, any>, edges: any[], levelGroups: { [key: number]: any[] }) => {
  const maxIterations = 50
  const coolingRate = 0.95
  let temperature = 100
  
  for (let iteration = 0; iteration < maxIterations; iteration++) {
    let totalImprovement = 0
    
    // 对每个非根节点进行位置微调
    Object.keys(levelGroups).forEach(levelStr => {
      const level = parseInt(levelStr)
      if (level === 0) return // 跳过根节点
      
      const levelNodes = levelGroups[level]
      
      levelNodes.forEach(node => {
        const currentX = node.x
        const currentY = node.y
        const currentScore = calculateNodeScore(node, nodeMap, edges)
        
        // 尝试小幅位置调整
        const adjustments = [
          { dx: temperature * 0.1, dy: 0 },
          { dx: -temperature * 0.1, dy: 0 },
          { dx: 0, dy: temperature * 0.1 },
          { dx: 0, dy: -temperature * 0.1 },
          { dx: temperature * 0.07, dy: temperature * 0.07 },
          { dx: -temperature * 0.07, dy: -temperature * 0.07 },
          { dx: temperature * 0.07, dy: -temperature * 0.07 },
          { dx: -temperature * 0.07, dy: temperature * 0.07 }
        ]
        
        let bestScore = currentScore
        let bestAdjustment: { dx: number, dy: number } | null = null
        
        adjustments.forEach(adj => {
          node.x = currentX + adj.dx
          node.y = currentY + adj.dy
          
          const newScore = calculateNodeScore(node, nodeMap, edges)
          if (newScore < bestScore) {
            bestScore = newScore
            bestAdjustment = adj
          }
        })
        
        if (bestAdjustment && bestAdjustment.dx !== undefined && bestAdjustment.dy !== undefined) {
          node.x = currentX + bestAdjustment.dx
          node.y = currentY + bestAdjustment.dy
          totalImprovement += currentScore - bestScore
        } else {
          node.x = currentX
          node.y = currentY
        }
      })
    })
    
    temperature *= coolingRate
    
    // 如果改善很小，提前结束
    if (totalImprovement < 1) break
  }
}

// 计算节点的布局质量分数（分数越低越好）
const calculateNodeScore = (node: any, nodeMap: Map<string, any>, edges: any[]) => {
  let score = 0
  
  // 获取与该节点相关的所有边
  const nodeEdges = edges.filter(edge => 
    edge.from === node.id || edge.to === node.id
  )
  
  nodeEdges.forEach(edge => {
    const otherNodeId = edge.from === node.id ? edge.to : edge.from
    const otherNode = nodeMap.get(otherNodeId)
    
    if (otherNode) {
      // 计算连接线长度（理想长度为150-200）
      const dx = node.x - otherNode.x
      const dy = node.y - otherNode.y
      const distance = Math.sqrt(dx * dx + dy * dy)
      
      const idealDistance = 180
      const lengthPenalty = Math.abs(distance - idealDistance) * 0.1
      score += lengthPenalty
      
      // 检查与其他连接线的交叉
      const crossingPenalty = calculateCrossingPenalty(
        { x: node.x, y: node.y },
        { x: otherNode.x, y: otherNode.y },
        nodeMap,
        edges
      )
      score += crossingPenalty * 10 // 交叉惩罚权重较高
    }
  })
  
  return score
}

// 计算连接线交叉惩罚
const calculateCrossingPenalty = (
  p1: {x: number, y: number}, 
  p2: {x: number, y: number}, 
  nodeMap: Map<string, any>, 
  edges: any[]
) => {
  let crossings = 0
  
  edges.forEach(edge => {
    const node1 = nodeMap.get(edge.from)
    const node2 = nodeMap.get(edge.to)
    
    if (node1 && node2) {
      const q1 = { x: node1.x, y: node1.y }
      const q2 = { x: node2.x, y: node2.y }
      
      // 检查两条线段是否相交
      if (lineSegmentsIntersect(p1, p2, q1, q2)) {
        crossings++
      }
    }
  })
  
  return crossings
}

// 检查两条线段是否相交
const lineSegmentsIntersect = (
  p1: {x: number, y: number}, 
  p2: {x: number, y: number},
  q1: {x: number, y: number}, 
  q2: {x: number, y: number}
) => {
  const ccw = (A: {x: number, y: number}, B: {x: number, y: number}, C: {x: number, y: number}) => {
    return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x)
  }
  
  return ccw(p1, q1, q2) !== ccw(p2, q1, q2) && ccw(p1, p2, q1) !== ccw(p1, p2, q2)
}

// 初始化Vis-Network图谱
const initVisNetwork = () => {
  if (!visContainer.value) return
  
  try {
    // 创建数据集
    visNodes = new DataSet([])
    visEdges = new DataSet([])
    
    const data = {
      nodes: visNodes,
      edges: visEdges
    }
    
         // 配置选项 - 径向布局优化的物理引擎
     const options = {
       physics: {
         enabled: true, // 启用物理引擎进行微调
         stabilization: {
           enabled: true,
           iterations: 300, // 减少迭代次数，布局已经预优化
           updateInterval: 25,
           onlyDynamicEdges: false,
           fit: true
         },
         barnesHut: {
           gravitationalConstant: -800, // 增加斥力，保持径向分布
           centralGravity: 0.02, // 极低中心引力，避免破坏径向结构
           springLength: 180, // 匹配理想连接线长度
           springConstant: 0.01, // 非常低的弹性，保持预设位置
           damping: 0.9, // 高阻尼，快速稳定
           avoidOverlap: 0.8 // 适度避免重叠
         },
         maxVelocity: 20, // 降低最大速度
         minVelocity: 0.1,
         timestep: 0.2, // 更小的时间步长
         solver: 'barnesHut'
       },
       nodes: {
         shape: 'dot',
         borderWidth: 3,
         borderWidthSelected: 4,
         font: {
           size: 16,
           color: isDarkTheme.value ? '#ffffff' : '#000000',
           face: 'Microsoft YaHei, Arial, sans-serif',
           strokeWidth: 4,
           vadjust: 0,
           align: 'center',
           multi: false,
           background: isDarkTheme.value ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)'
         },
         labelHighlightBold: false,
         chosen: true,
         // 强制文字在节点内部 - 使用特殊配置
         margin: {
           top: 0,
           right: 0,
           bottom: 0,
           left: 0
         },
         widthConstraint: false,
         heightConstraint: false,
         // 立体感阴影效果
         shadow: {
           enabled: true,
           color: 'rgba(0,0,0,0.4)',
           size: 25,
           x: 5,
           y: 5
         },
         // 缩放设置 - 优化文字显示
         scaling: {
           min: 20,
           max: 100,
           label: {
             enabled: true,
             min: 8,
             max: 20,
             maxVisible: 50,
             drawThreshold: 3
           }
         }
       },
       edges: {
        // 优化径向布局的连接线
        smooth: {
          enabled: true,
          type: 'continuous',
          forceDirection: 'none',
          roundness: 0.1 // 轻微弯曲，避免重叠
        },
         physics: true,
        length: 180, // 匹配理想连接线长度
        width: 4,
        selectionWidth: 6,
        hoverWidth: 5,
         color: {
          color: '#0f172a', // 使用深黑色，最高对比度
          highlight: '#dc2626', // 高亮时深红色
          hover: '#0f172a'
         },
         arrows: {
           to: {
             enabled: true,
             scaleFactor: 2.5,
             type: 'arrow'
           }
         },
         shadow: {
           enabled: true,
           color: 'rgba(0,0,0,0.5)',
           size: 20,
           x: 5,
           y: 5
         }
       },
      interaction: {
        dragNodes: true,
        dragView: true,
        zoomView: true,
        hover: true,
        hoverConnectedEdges: true,
        selectConnectedEdges: true,
        tooltipDelay: 300
      },
      layout: {
        improvedLayout: false, // 禁用自动布局
        hierarchical: {
          enabled: false // 我们使用自定义分层布局
        }
      },
      configure: {
        enabled: false
      },
      // 设置背景色
      background: {
        color: isDarkTheme.value ? '#000000' : '#ffffff',
        image: null
      }
    }
    
         // 创建网络
     visNetwork = new Network(visContainer.value, data, options)
     
     // 监听网络渲染完成
     visNetwork.on('stabilized', () => {
   
       // 如果动画应该持续运行，添加随机扰动
       if (is2DAnimating.value && visNetwork) {

         setTimeout(() => {
           if (visNetwork && is2DAnimating.value) {
             // 随机调整物理参数，防止稳定化
             const randomFactor = 0.9 + Math.random() * 0.2 // 0.9 到 1.1 的随机因子
             visNetwork.setOptions({
               physics: {
                 barnesHut: {
                   gravitationalConstant: -2000 * randomFactor,
                   centralGravity: 0.1 * randomFactor,
                   springLength: 95 * randomFactor,
                   springConstant: 0.04 * randomFactor,
                   damping: 0.09 * randomFactor,
                   avoidOverlap: 0.5 * randomFactor
                 }
               }
             })

           }
         }, 100)
       }
       
       // 强制重绘以显示自定义文字
       setTimeout(() => {
         if (visNetwork) {
           visNetwork.redraw()
         }
       }, 100)
     })
     
     // 监听缩放和移动事件，确保文字始终正确显示
     visNetwork.on('zoom', () => {
       if (visNetwork) {
         visNetwork.redraw()
       }
     })
     
     visNetwork.on('dragEnd', () => {
       if (visNetwork) {
         visNetwork.redraw()
       }
     })
     
     // 添加自定义绘制确保文字在节点中心
     visNetwork.on('afterDrawing', (ctx) => {
       if (ctx && visNodes && visNetwork) {
         const positions = visNetwork.getPositions()
         
         // 重新绘制每个节点的文字在正确位置
         visNodes.get().forEach(node => {
           const position = positions[node.id]
           if (position && node.customLabel) {
             // 根据节点大小动态设置字体大小
             let fontSize = 12 // 默认字体大小
             if (node.size >= 70) {
               fontSize = 16 // 大节点
             } else if (node.size >= 50) {
               fontSize = 14 // 中等节点
             } else if (node.size >= 35) {
               fontSize = 12 // 中小节点
             } else {
               fontSize = 10 // 小节点
             }
             
             // 设置文字样式
             ctx.font = `${fontSize}px Microsoft YaHei, Arial, sans-serif`
             ctx.fillStyle = '#ffffff'
             ctx.textAlign = 'center'
             ctx.textBaseline = 'middle'
             ctx.strokeStyle = 'rgba(0,0,0,0.8)'
             ctx.lineWidth = 3
             
             // 在节点中心绘制文字
             ctx.strokeText(node.customLabel, position.x, position.y)
             ctx.fillText(node.customLabel, position.x, position.y)
           }
         })
       }
     })
    
    // 设置初始缩放和位置 - 让图谱不要太大
    visNetwork.once('stabilizationIterationsDone', () => {
      if (visNetwork) {
        visNetwork.fit({
          animation: {
            duration: 1000,
            easingFunction: 'easeInOutQuad'
          }
        })
        // 缩小到合适大小
        const scale = visNetwork.getScale()
        visNetwork.moveTo({
          scale: scale * 0.4, // 缩小到60%
          animation: {
            duration: 800,
            easingFunction: 'easeInOutQuad'
          }
        })
        
        // 如果动画应该运行，启动定时器
        if (is2DAnimating.value) {
          start2DAnimationTimer()
        }
      }
    })
    
    // 设置背景色
    if (visContainer.value) {
      visContainer.value.style.backgroundColor = isDarkTheme.value ? '#000000' : '#ffffff'
    }
    
    // 监听节点点击事件
    visNetwork.on('click', async (params: any) => {
      if (params.nodes.length > 0) {
        const nodeId = params.nodes[0]
        
        // 从visNodes中获取节点的完整信息，包括名称
        let nodeName = nodeId // 默认使用ID作为名称
        if (visNodes) {
          const nodeData = visNodes.get(nodeId) as any
          if (nodeData && nodeData.customLabel) {
            nodeName = nodeData.customLabel // 使用节点的实际名称
          }
        }
        
      
        
        // 查找右侧图谱节点数据，检查 point 字段
        const nodeData = findRightGraphNodeById(nodeId)
       
        
        // 如果节点的tag字段为'point'，调用意识形态教育链接接口
        if (nodeData && nodeData.tag === 'point') {
        
          await handleIdeologyEducationLinks(nodeName)
        }
        // 注意：与3D图谱保持一致，不调用handleNodeFocus，只处理point类型的节点
      } else {
        // 点击空白区域，取消聚焦，显示所有节点
        clearNodeFocus()
      }
    })
    
    // 监听节点悬浮事件
    visNetwork.on('hoverNode', (params: any) => {
      const nodeId = params.node
   
    })
    
    visNetwork.on('blurNode', (params: any) => {
      const nodeId = params.node
  
    })
    
  } catch (error) {

  }
}

// 颜色调整函数 - 用于生成立体感渐变
const adjustColorBrightness = (color: string, amount: number): string => {
  // 如果是十六进制颜色
  if (color.startsWith('#')) {
    const hex = color.slice(1)
    const r = parseInt(hex.slice(0, 2), 16)
    const g = parseInt(hex.slice(2, 4), 16)
    const b = parseInt(hex.slice(4, 6), 16)
    
    const newR = Math.max(0, Math.min(255, r + amount))
    const newG = Math.max(0, Math.min(255, g + amount))
    const newB = Math.max(0, Math.min(255, b + amount))
    
    return `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}`
  }
  return color
}

// 生成Vis-Network图谱数据
const generateVisNetworkData = () => {
  const graphDataResult = generate3DGraphData()
  
  // 安全检查：确保nodes和links存在
  if (!graphDataResult || !graphDataResult.nodes || !Array.isArray(graphDataResult.nodes) || !graphDataResult.links || !Array.isArray(graphDataResult.links)) {
    return { nodes: [], edges: [] }
  }
  
  // 同步右侧图谱节点映射（2D模式）
  rightGraphNodeMap.clear()
  graphDataResult.nodes.forEach((n: any) => rightGraphNodeMap.set(n.id, n))
  
  // 创建立体感节点数据 - 文字在球体内部居中
  const nodes = graphDataResult.nodes.map((node: any) => {
    // 根据层级设置大小，层级越高（数字越小）节点越大
    const category = node.category || 0
    let size = 60 // 基础大小
    
    // 按照层级设置大小：层级0最大，层级越高节点越小
    if (category === 0) {
      size = 80 // 一级节点：最大
    } else if (category === 1) {
      size = 60 // 二级节点：较大
    } else if (category === 2) {
      size = 45 // 三级节点：中等
    } else if (category === 3) {
      size = 35 // 四级节点：较小
    } else if (category === 4) {
      size = 28 // 五级节点：小
    } else if (category === 5) {
      size = 22 // 六级节点：更小
    } else if (category === 6) {
      size = 18 // 七级节点：很小
    } else if (category === 7) {
      size = 15 // 八级节点：非常小
    } else if (category === 8) {
      size = 12 // 九级节点：极小
    } else {
      size = 10 // 十级及以上：最小
    }
    
    // 计算字体大小，根据球体大小动态调整，确保文字清晰可见
    // 大节点使用更大字体，小节点使用更小但清晰的字体
    let fontSize
    if (size >= 70) {
      fontSize = 16 // 大节点
    } else if (size >= 50) {
      fontSize = 14 // 中等节点
    } else if (size >= 35) {
      fontSize = 12 // 中小节点
    } else {
      fontSize = 10 // 小节点
    }
    
    // 生成立体感的渐变色效果
    const baseColor = node.color
    const lighterColor = adjustColorBrightness(baseColor, 40)
    
    return {
      id: node.id,
      label: '', // 禁用默认标签避免重复
      title: node.name, // 悬停时显示名称
      customLabel: node.name, // 自定义属性存储文字
      shape: 'dot',
      color: {
        background: baseColor,
        border: adjustColorBrightness(baseColor, -40), // 使用更深的边框色
        highlight: {
          background: lighterColor,
          border: '#1e40af' // 蓝色边框
        },
        hover: {
          background: lighterColor,
          border: adjustColorBrightness(baseColor, -40) // 深色边框
        }
      },
      size: size,
      borderWidth: 3,
      borderWidthSelected: 4,
      font: {
        size: Math.max(fontSize, 16), // 确保最小字体大小为16
        color: isDarkTheme.value ? '#ffffff' : '#000000',
        strokeWidth: 4,
        face: 'Microsoft YaHei, Arial, sans-serif',
        vadjust: 0,
        align: 'center',
        multi: false,
        background: isDarkTheme.value ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)'
      },
      // 强制标签在节点内部显示
      labelHighlightBold: false,
      chosen: {
        node: true,
        label: true
      },
      // 文字在节点内部，不换行
      margin: {
        top: 0,
        right: 0,
        bottom: 0,
        left: 0
      },
      widthConstraint: false,
      heightConstraint: false,
      // 强化立体感阴影
      shadow: {
        enabled: true,
        color: 'rgba(0,0,0,0.8)',
        size: Math.max(15, size * 0.25),
        x: 4,
        y: 4
      },
      // 缩放效果
      scaling: {
        min: 0.8,
        max: 1.3
      }
    }
  })
  
  // 分层网格布局的边数据 - 直线连接，不交叉
  const edges = graphDataResult.links.map((link: any, index: number) => {
    return {
      id: `edge-${index}`,
      from: link.source,
      to: link.target,
      // 直线连接，分层网格布局天然不交叉
      smooth: false,
      physics: true,
      length: 250, // 匹配层级间距
      width: 2,
      color: {
        color: 'rgba(255, 255, 255, 0.8)',
        highlight: 'rgba(255, 255, 255, 1.0)',
        hover: 'rgba(255, 255, 255, 0.9)'
      },
      arrows: {
        to: {
          enabled: true,
          scaleFactor: 1.5,
          type: 'arrow'
        }
      },
      shadow: {
        enabled: true,
        color: 'rgba(0,0,0,0.5)',
        size: 10,
        x: 3,
        y: 3
      }
    }
  })
  
  return { nodes, edges }
}

// 更新Vis-Network图谱
const updateVisNetwork = () => {
  if (!visNetwork) {
    initVisNetwork()
  }
  
  if (!visNetwork || !visNodes || !visEdges) return
  
  try {
    const data = generateVisNetworkData()
    
    // 检查数据有效性
    if (!data || !data.nodes || !Array.isArray(data.nodes)) {
      return
    }
    
    // 应用分层布局算法
    const layoutData = calculateHierarchicalLayout(data.nodes, data.edges)
    
    // 清空现有数据
    if (visNodes && visEdges) {
      visNodes.clear()
      visEdges.clear()
    }
    
    // 添加新数据
    if (visNodes && visEdges) {
      visNodes.add(layoutData)
      visEdges.add(data.edges)
    }
    
    // 立即适应视图，不延迟
    if (visNetwork) {
      visNetwork.fit({
        animation: {
          duration: 500, // 缩短动画时间
          easingFunction: 'easeInOutQuad'
        }
      })
      
      // 调整到合适的缩放比例
      setTimeout(() => {
        if (visNetwork) {
          const scale = visNetwork.getScale()
          visNetwork.moveTo({
            scale: scale * 0.8, // 设置合适的缩放比例，让视角更大
            animation: {
              duration: 300,
              easingFunction: 'easeInOutQuad'
            }
          })
        }
      }, 100)
      
      // 如果动画应该运行，立即启动定时器
      if (is2DAnimating.value) {
        start2DAnimationTimer()
      }
    }
    
  } catch (error) {
  }
}

// 控制函数
const toggleGraphMode = () => {
  is3DMode.value = !is3DMode.value
  
  nextTick(() => {
    if (is3DMode.value) {
      // 切换到3D模式
      if (visNetwork) {
        visNetwork.destroy()
        visNetwork = null
        visNodes = null
        visEdges = null
      }
      initThreeJS()
      update3DGraph()
    } else {
      // 切换到2D模式
      if (animationId) {
        cancelAnimationFrame(animationId)
      }
      updateVisNetwork()
      
      // 确保2D动画定时器启动
      setTimeout(() => {
        if (is2DAnimating.value) {
          start2DAnimationTimer()
        }
      }, 1000)
    }
  })
}

const reset2DView = () => {
  if (visNetwork) {
    visNetwork.fit({
      animation: {
        duration: 500, // 缩短动画时间
        easingFunction: 'easeInOutQuad'
      }
    })
    
    // 调整到合适的缩放比例
    setTimeout(() => {
      if (visNetwork) {
        const scale = visNetwork.getScale()
        visNetwork.moveTo({
          scale: scale * 0.8, // 设置合适的缩放比例，让视角更大
          animation: {
            duration: 300,
            easingFunction: 'easeInOutQuad'
          }
        })
      }
    }, 100)
  }
}

// 启动2D动画定时器
const start2DAnimationTimer = () => {
  if (animationTimer) {
    clearInterval(animationTimer)
  }
  
  animationTimer = setInterval(() => {
    if (visNetwork && is2DAnimating.value) {
      // 持续的参数微调，保持动画活力
      try {
        const randomFactor = 0.9 + Math.random() * 0.2 // 0.9 到 1.1 的随机因子，保持变化
        visNetwork.setOptions({
          physics: {
            barnesHut: {
              gravitationalConstant: -1200 * randomFactor,
              centralGravity: 0.15 * randomFactor,
              springLength: 300 * randomFactor,
              springConstant: 0.03 * randomFactor,
              damping: 0.1 * randomFactor,
              avoidOverlap: 0.3 * randomFactor
            },
            minVelocity: 0.2 // 确保最小速度，防止停止
          }
        })
      } catch (error) {
        console.error('物理参数调整失败:', error)
      }
    }
  }, 3000) // 每3秒调整一次参数，保持活力
}

// 停止2D动画定时器
const stop2DAnimationTimer = () => {
  if (animationTimer) {
    clearInterval(animationTimer)
    animationTimer = null
  }
}

const toggle2DAnimation = () => {
  is2DAnimating.value = !is2DAnimating.value
  console.log('切换动画状态:', is2DAnimating.value)
  
  if (visNetwork) {
    if (is2DAnimating.value) {
      // 启动动画 - 使用更激进的参数确保有明显运动
      console.log('启动2D动画')
      try {
        // 先禁用物理引擎
        visNetwork.setOptions({
          physics: { enabled: false }
        })
        
        // 延迟启用动画物理引擎
        setTimeout(() => {
          if (visNetwork) {
            visNetwork.setOptions({
              physics: {
                enabled: true,
                barnesHut: {
                  gravitationalConstant: -3000, // 强引力但不过度
                  centralGravity: 0.5, // 适中中心引力
                  springLength: 200, // 适中弹簧长度
                  springConstant: 0.1, // 适中弹性
                  damping: 0.1, // 低阻尼保持运动
                  avoidOverlap: 0.2 // 允许一定重叠
                },
                maxVelocity: 60, // 适中最大速度
                minVelocity: 2.0, // 保证最小运动
                solver: 'barnesHut',
                stabilization: { enabled: false },
                timestep: 0.3,
                adaptiveTimestep: true
              }
            })
            console.log('动画物理引擎已启动')
          }
        }, 200)
        
        // 强制重启物理引擎
        visNetwork.stopSimulation()
        setTimeout(() => {
          if (visNetwork) {
            visNetwork.startSimulation()
            console.log('物理引擎已重启')
          }
        }, 100)
        
        start2DAnimationTimer()
      } catch (error) {
        console.error('启动动画失败:', error)
      }
    } else {
      // 停止动画
      console.log('停止2D动画')
      try {
        visNetwork.setOptions({
        physics: {
          enabled: true,
          barnesHut: {
            gravitationalConstant: -500,
            centralGravity: 0.05,
            springLength: 200,
            springConstant: 0.01,
            damping: 0.95, // 极高阻尼快速停止
            avoidOverlap: 1.0
          },
          maxVelocity: 5,
          minVelocity: 0.001,
          solver: 'barnesHut',
          stabilization: { 
            enabled: true,
            iterations: 300,
            updateInterval: 25
          }
        }
        })
        stop2DAnimationTimer()
      } catch (error) {
        console.error('停止动画失败:', error)
      }
    }
  }
}

// ==================== 2D图谱函数结束 ====================

onMounted(async () => {
  nextTick(async () => {
    // 初始化CSS变量
    document.documentElement.style.setProperty('--panel-width', panelWidth.value + 'px')
    
    // 首先获取知识图谱数据
    await fetchGraphData()
    
    // 初始化展开所有分类节点
    initExpandedCategories()
    
    // 确保API数据获取成功后再初始化图谱
    if (graphData.value || treeData.value) {
      if (is3DMode.value) {
        initThreeJS()
        update3DGraph()
      } else {
        updateVisNetwork()
      }
    } else {
      if (is3DMode.value) {
        initThreeJS()
        update3DGraph()
      } else {
        updateVisNetwork()
      }
    }
    window.addEventListener('resize', handleResize)
  })
})

onUnmounted(() => {
  // 清理3D动画
  if (animationId) {
    cancelAnimationFrame(animationId)
  }
  // 清理2D动画定时器
  stop2DAnimationTimer()
  // 清理Vis-Network图谱
  if (visNetwork) {
    visNetwork.destroy()
    visNetwork = null
    visNodes = null
    visEdges = null
  }
  if (renderer && chartRef.value) {
    renderer.domElement.removeEventListener('click', onMouseClick)
    renderer.domElement.removeEventListener('mousemove', onMouseMove)
    renderer.domElement.removeEventListener('mouseleave', onMouseLeave)
    chartRef.value.removeChild(renderer.domElement)
    renderer.dispose()
  }
  if (tooltipElement) {
    document.body.removeChild(tooltipElement)
  }
  window.removeEventListener('resize', handleResize)
})
</script>

<style scoped>
.knowledge-graph {
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--minimal-bg);
  position: relative;
}

/* 全屏模式样式 */
.knowledge-graph.fullscreen-mode {
  position: fixed !important;
  top: 0 !important;
  left: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  width: 100vw !important;
  height: 100vh !important;
  z-index: 9999 !important;
  background: var(--minimal-bg) !important;
  padding: 0 !important; /* 重置所有内边距 */
  margin: 0 !important; /* 重置所有边距 */
  box-sizing: border-box !important; /* 确保边框和内边距包含在尺寸内 */
  overflow: hidden !important; /* 防止滚动条 */
}

.knowledge-graph.fullscreen-mode .knowledge-selector {
  display: block !important; /* 全屏时保持左侧面板可见 */
  width: 300px !important; /* 默认左侧面板宽度 */
  min-width: 200px !important;
  max-width: 600px !important;
  height: 100vh !important; /* 全屏时占满整个高度 */
  overflow-y: auto !important; /* 如果内容过多，允许滚动 */
  border-right: 1px solid var(--minimal-border) !important;
  background: var(--minimal-bg) !important;
  padding: 1rem !important; /* 恢复与原始样式一致的内边距 */
  margin: 0 !important; /* 重置边距 */
  box-sizing: border-box !important; /* 确保内边距包含在宽度内 */
}

.knowledge-graph.fullscreen-mode .graph-right {
  flex: 1 !important;
  width: calc(100vw - var(--panel-width, 300px)) !important; /* 使用CSS变量动态计算宽度 */
  height: 100vh !important;
  padding: 0 !important; /* 重置内边距 */
  margin: 0 !important; /* 重置边距 */
  overflow: hidden !important; /* 防止滚动条 */
}

.knowledge-graph.fullscreen-mode .graph-chart {
  width: 100% !important;
  height: 100% !important;
  padding: 0 !important; /* 重置内边距 */
  margin: 0 !important; /* 重置边距 */
}

/* 全屏模式下左侧面板收起时的样式 */
.knowledge-graph.fullscreen-mode .knowledge-selector.collapsed {
  width: 0 !important;
  min-width: 0 !important;
  max-width: 0 !important;
  padding: 0 !important;
  border-right: none !important;
  overflow: visible !important; /* 允许按钮显示 */
}

.knowledge-graph.fullscreen-mode .knowledge-selector.collapsed .selector-content {
  display: none !important;
}

.knowledge-graph.fullscreen-mode .knowledge-selector.collapsed + .graph-right {
  width: 100vw !important;
}

/* 全屏模式下收起按钮始终可见 */
.knowledge-graph.fullscreen-mode .knowledge-selector.collapsed .collapse-btn {
  position: fixed !important;
  top: 1rem !important;
  left: 0.5rem !important;
  z-index: 1000 !important;
  background: #5B7CFF !important;
  color: white !important;
  border: none !important;
  border-radius: 50% !important;
  width: 32px !important;
  height: 32px !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  cursor: pointer !important;
  box-shadow: 0 2px 8px rgba(91, 124, 255, 0.3) !important;
  font-size: 16px !important;
  font-weight: bold !important;
  opacity: 1 !important;
  visibility: visible !important;
}

/* 全屏模式下的展开按钮样式 */
.knowledge-graph.fullscreen-mode .expand-btn-fullscreen {
  position: fixed !important;
  top: 1rem !important;
  left: 1rem !important;
  z-index: 9999 !important;
  background: #5B7CFF !important;
  color: white !important;
  border: none !important;
  border-radius: 50% !important;
  width: 40px !important;
  height: 40px !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  cursor: pointer !important;
  box-shadow: 0 4px 12px rgba(91, 124, 255, 0.4) !important;
  font-size: 20px !important;
  font-weight: bold !important;
  transition: all 0.3s ease !important;
  opacity: 1 !important;
  visibility: visible !important;
}

.knowledge-graph.fullscreen-mode .expand-btn-fullscreen:hover {
  background: #4A6BEF !important;
  transform: scale(1.1) !important;
  box-shadow: 0 6px 16px rgba(91, 124, 255, 0.5) !important;
}

.expand-icon {
  transition: transform 0.3s ease;
  display: inline-block;
}

/* 加载状态样式 */
.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

/* 右侧图谱加载动画 */
.graph-loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  border-radius: 8px;
}

.graph-loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: rgba(0, 0, 0, 0.05);
  padding: 30px 40px;
  border-radius: 12px;
  border: 1px solid rgba(0, 0, 0, 0.1);
}

.graph-loading-container .loading-text {
  color: #333333;
  font-size: 14px;
  margin-top: 16px;
}

.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: rgba(255, 255, 255, 0.9);
  padding: 40px 60px;
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #e3e3e3;
  border-top: 3px solid #007bff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 16px;
}

.loading-text {
  color: #333;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.5px;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* 错误状态样式 */
.error-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.error-container {
  background: rgba(255, 255, 255, 0.9);
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  overflow: hidden;
  max-width: 400px;
  width: 90%;
}

.error-content {
  display: flex;
  align-items: center;
  padding: 24px;
  gap: 16px;
}

.error-icon {
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  background: #ff4757;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
}

.error-info {
  flex: 1;
  min-width: 0;
}

.error-title {
  color: #333;
  font-size: 16px;
  font-weight: 600;
  margin-bottom: 4px;
}

.error-text {
  color: #666;
  font-size: 14px;
  line-height: 1.4;
  word-break: break-word;
}

.retry-btn {
  flex-shrink: 0;
  background: #007bff;
  color: #fff;
  border: none;
  padding: 8px 16px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: all 0.2s ease;
  white-space: nowrap;
}

.retry-btn:hover {
  background: #0056b3;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
}

.retry-btn:active {
  transform: translateY(0);
}

.graph-container {
  flex: 1;
  display: flex;
  overflow: hidden;
  width: 100%;
  height: 100vh;
}

.graph-right {
  flex: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
  height: 100%;
  transition: width 0.2s ease;
}

/* 当左侧面板收起时，右侧区域应该占满剩余空间 */
.knowledge-selector.collapsed + .graph-right {
  flex: 1 !important;
  width: calc(100% - 48px) !important;
  min-width: calc(100% - 48px) !important;
  max-width: calc(100% - 48px) !important;
}

/* 确保收起状态下容器布局正确 */
.graph-container:has(.knowledge-selector.collapsed) .graph-right {
  flex: 1 !important;
  width: calc(100% - 48px) !important;
  min-width: calc(100% - 48px) !important;
}

.graph-chart {
  flex: 1;
  position: relative;
  background: #ffffff;
  overflow: hidden;
  width: 100%;
  height: 100%;
  min-width: 0;
  transition: background-color 0.3s ease;
}

/* 星空主题样式 */
.knowledge-graph.dark-theme .graph-chart {
  background: #000000;
}

/* 主题切换按钮样式 */
.theme-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  transition: all 0.3s ease;
}

.theme-toggle:hover {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}

/* 加载状态主题样式 */
.graph-loading-container.light-theme {
  background: rgba(0, 0, 0, 0.05);
  border: 1px solid rgba(0, 0, 0, 0.1);
}

.graph-loading-container.dark-theme {
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.graph-loading-container.dark-theme .loading-text {
  color: #ffffff;
}

.vis-container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #ffffff;
  z-index: 1;
  transition: background-color 0.3s ease;
}

/* 星空主题下的2D图谱背景 */
.knowledge-graph.dark-theme .vis-container {
  background: #000000;
}

/* 为vis-network画布添加专业效果 */
.vis-container canvas {
  background: #ffffff !important;
  transition: background-color 0.3s ease;
}

/* 星空主题下的画布背景 */
.knowledge-graph.dark-theme .vis-container canvas {
  background: #000000 !important;
}

/* 添加星空背景点缀 */
/* 移除背景装饰，保持纯白色背景 */

@keyframes twinkle {
  0% { opacity: 0.2; }
  100% { opacity: 0.6; }
}

.controls-overlay {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.control-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: rgba(255, 255, 255, 0.1);
  padding: 12px;
  border-radius: 12px;
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.graph-header {
  padding: 1.5rem 3rem 1.5rem 0;
  border-bottom: 1px solid rgba(91, 124, 255, 0.08);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.section-title {
  display: flex;
  align-items: center;
}

.title-bar {
  width: 4px;
  height: 20px;
  background: var(--minimal-primary-gradient);
  border-radius: 2px;
  margin-right: 12px;
}

.section-title h2 {
  font-size: 1.3em;
  font-weight: 600;
  color: var(--minimal-text);
  margin: 0;
}

.graph-controls {
  display: flex;
  align-items: center;
  gap: 2rem;
}

.legend {
  display: flex;
  gap: 1.5rem;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9rem;
  color: var(--minimal-text);
}

.legend-color {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  position: relative;
}

.legend-color::after {
  content: '';
  position: absolute;
  top: -2px;
  left: -2px;
  right: -2px;
  bottom: -2px;
  border-radius: 50%;
  background: inherit;
  filter: blur(4px);
  opacity: 0.6;
  z-index: -1;
}

.graph-container {
  flex: 1;
  display: flex;
  overflow: hidden;
}

.graph-right {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  position: relative;
}

.graph-chart {
  flex: 1;
  height: 100%;
  transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  transform: translateZ(0);
  background: radial-gradient(ellipse at center, #1a1a2e 0%, #16213e 50%, #0f0f23 100%);
  position: relative;
  overflow: hidden;
}

/* 添加星空背景效果 */
.graph-chart::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-image: 
    radial-gradient(2px 2px at 20px 30px, #eee, transparent),
    radial-gradient(2px 2px at 40px 70px, rgba(255,255,255,0.8), transparent),
    radial-gradient(1px 1px at 90px 40px, #fff, transparent),
    radial-gradient(1px 1px at 130px 80px, rgba(255,255,255,0.6), transparent),
    radial-gradient(2px 2px at 160px 30px, #ddd, transparent);
  background-repeat: repeat;
  background-size: 200px 100px;
  animation: twinkle 4s ease-in-out infinite alternate;
  pointer-events: none;
  z-index: 0;
}

@keyframes twinkle {
  0% { opacity: 0.3; }
  100% { opacity: 1; }
}

.controls-overlay {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 10;
}

.control-group {
  display: flex;
  gap: 10px;
}

.control-btn {
  padding: 8px 16px;
  background: rgba(255, 255, 255, 0.9);
  color: #333;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 6px;
  cursor: pointer;
  font-size: 12px;
  transition: all 0.2s ease;
  backdrop-filter: blur(10px);
}

.control-btn:hover {
  background: rgba(255, 255, 255, 1);
  border-color: rgba(0, 0, 0, 0.2);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.mode-toggle {
  background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
  color: white;
  border: 1px solid rgba(255, 255, 255, 0.2);
  font-weight: bold;
}

.fullscreen-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
  color: white;
  border: 1px solid rgba(255, 255, 255, 0.2);
  font-weight: bold;
}

.fullscreen-btn:hover {
  background: linear-gradient(135deg, #059669 0%, #047857 100%);
  border-color: rgba(255, 255, 255, 0.3);
  box-shadow: 0 2px 12px rgba(16, 185, 129, 0.3);
}

.mode-toggle:hover {
  background: linear-gradient(135deg, #5856eb 0%, #7c3aed 100%);
  border-color: rgba(255, 255, 255, 0.3);
  box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}

.knowledge-selector {
  width: 320px;
  min-width: 200px;
  max-width: 600px;
  padding: 1rem;
  background: rgba(255, 255, 255, 0.95);
  border-right: 1px solid rgba(0, 0, 0, 0.1);
  overflow: hidden;
  position: relative;
  transition: min-width 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  transform: translateZ(0);
  height: 95%;
}

.knowledge-selector.collapsed {
  min-width: 48px !important;
  width: 48px !important;
  max-width: 48px !important;
  padding: 1rem 0.5rem;
  flex-shrink: 0;
  overflow: hidden;
  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
  border-right: 2px solid #e2e8f0;
}

.selector-content {
  opacity: 1;
  transform: translateX(0);
  transition: opacity 0.15s ease, transform 0.15s ease;
  min-height: fit-content;
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* 确保不影响子容器的滚动 */
  overflow: visible;
  height: 100%;
}

.knowledge-selector.collapsed .selector-content {
  opacity: 0;
  transform: translateX(-10px);
  pointer-events: none;
}

/* 知识库标题样式 */
.knowledge-title {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 16px;
  font-weight: 600;
  color: #333;
}

.title-line {
  width: 4px;
  height: 20px;
  background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
  border-radius: 2px;
}

/* 搜索框样式 */
.knowledge-search {
  display: flex;
  flex-direction: column;
  gap: 8px;
  position: relative;
}

.search-input-wrapper {
  display: flex;
  gap: 8px;
  align-items: center;
}

.knowledge-search-input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  font-size: 14px;
  background: white;
  outline: none;
  transition: border-color 0.2s ease;
}

.knowledge-search-input:focus {
  border-color: #6366f1;
  box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
}

.knowledge-search-btn {
  padding: 8px 12px;
  background: #6366f1;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 14px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: background-color 0.2s ease;
}

.knowledge-search-btn:hover {
  background: #5856eb;
}

.search-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  z-index: 1000;
  max-height: 200px;
  overflow-y: auto;
}

.search-dropdown-item {
  padding: 10px 12px;
  cursor: pointer;
  border-bottom: 1px solid #f3f4f6;
  transition: background-color 0.2s ease;
  font-size: 14px;
}

.search-dropdown-item:last-child {
  border-bottom: none;
}

.search-dropdown-item:hover {
  background-color: #f8fafc;
}

.search-dropdown-item:active {
  background-color: #e2e8f0;
}

/* 清除搜索按钮样式 */
.search-clear {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px 10px;
  background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
  border: 1px solid #cbd5e1;
  border-radius: 6px;
  color: #64748b;
  font-size: 12px;
  cursor: pointer;
  transition: all 0.2s ease;
  align-self: flex-start;
  margin-top: 4px;
}

.search-clear:hover {
  background: linear-gradient(135deg, #e2e8f0 0%, #cbd5e1 100%);
  border-color: #94a3b8;
  color: #475569;
  transform: translateY(-1px);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.search-clear:active {
  transform: translateY(0);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}

.search-clear svg {
  flex-shrink: 0;
}

/* 节点高亮样式 */
.node-highlight {
  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) !important;
  border: 2px solid #f59e0b !important;
  border-radius: 8px !important;
  box-shadow: 0 0 20px rgba(245, 158, 11, 0.3) !important;
  animation: highlight-pulse 2s ease-in-out;
}

@keyframes highlight-pulse {
  0% {
    box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
  }
  50% {
    box-shadow: 0 0 0 10px rgba(245, 158, 11, 0.3);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
  }
}

.collapse-btn {
  position: absolute;
  top: 1rem;
  right: 0.5rem;
  width: 28px;
  height: 28px;
  border: none;
  background: #5B7CFF;
  color: white;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.2s ease;
  z-index: 10;
  box-shadow: 0 2px 8px rgba(91, 124, 255, 0.3);
  font-size: 16px;
  font-weight: bold;
}

.collapse-btn:hover {
  background: #4A6BEF;
  transform: scale(1.1);
  box-shadow: 0 4px 12px rgba(91, 124, 255, 0.4);
}

.collapse-icon {
  transition: transform 0.3s ease;
  display: inline-block;
}

.collapse-icon.rotated {
  transform: rotate(180deg);
}

/* 拖拽手柄样式 */
.resize-handle {
  position: absolute;
  top: 0;
  right: 0;
  width: 4px;
  height: 100%;
  background: transparent;
  cursor: col-resize;
  z-index: 10;
  transition: background-color 0.2s ease;
}

.resize-handle:hover {
  background: #5B7CFF;
}

.resize-handle::before {
  content: '';
  position: absolute;
  top: 50%;
  right: -2px;
  transform: translateY(-50%);
  width: 8px;
  height: 40px;
  background: #5B7CFF;
  border-radius: 4px;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.resize-handle:hover::before {
  opacity: 0.6;
}

/* 拖拽时的样式 */
.knowledge-selector.resizing {
  user-select: none;
  pointer-events: none;
}

.knowledge-selector.resizing .resize-handle {
  background: #5B7CFF;
}

.knowledge-selector.resizing .resize-handle::before {
  opacity: 1;
}

/* 知识库分类列表样式 */
.knowledge-categories {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  padding-right: 8px;
  height: calc(100vh - 200px);
  /* 确保内容不被截断 */
  min-width: 0;
  /* 确保滚动条显示 */
  position: relative;
}

.knowledge-categories::-webkit-scrollbar {
  width: 8px;
  height: 0px;
}

.knowledge-categories::-webkit-scrollbar-track {
  background: #f1f1f1;
  border-radius: 4px;
}

.knowledge-categories::-webkit-scrollbar-thumb {
  background: #c1c1c1;
  border-radius: 4px;
}

.knowledge-categories::-webkit-scrollbar-thumb:hover {
  background: #a8a8a8;
}

/* 确保滚动条始终可见 */
.knowledge-categories {
  scrollbar-width: thin;
  scrollbar-color: #c1c1c1 #f1f1f1;
}






@media (max-width: 768px) {
  .graph-header {
    padding: 1.5rem;
    flex-direction: column;
    gap: 1rem;
    align-items: flex-start;
  }

  .graph-container {
    flex-direction: column;
  }

  .knowledge-selector {
    width: 100%;
    flex-direction: row;
    padding: 1rem;
    height: auto;
    max-height: 300px;
  }

  .knowledge-categories {
    height: 200px;
    overflow-x: hidden;
  }

  .legend {
    flex-wrap: wrap;
    gap: 1rem;
  }
}

/* Vis-Network 文字在球体内部且不换行 */
.vis-container :deep(.vis-network) {
  font-family: 'Microsoft YaHei', Arial, sans-serif !important;
}

/* 强制文字在节点中心且不换行 */
.vis-container :deep(text) {
  text-anchor: middle !important;
  dominant-baseline: central !important;
  white-space: nowrap !important;
}

/* 确保文字在节点内部显示 */
.vis-container :deep(.vis-label) {
  text-align: center !important;
  white-space: nowrap !important;
}

/* 强制节点文字居中 */
.vis-container :deep(.vis-node text) {
  text-anchor: middle !important;
  dominant-baseline: middle !important;
  white-space: nowrap !important;
}

/* 抽屉样式 */
.knowledge-drawer {
  font-family: 'Microsoft YaHei', Arial, sans-serif;
}

.drawer-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px;
  text-align: center;
}

.drawer-loading .loading-spinner {
  width: 32px;
  height: 32px;
  border: 3px solid #f3f3f3;
  border-top: 3px solid #3a7bd5;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 16px;
}

.drawer-loading p {
  margin-top: 16px;
  color: #666;
  font-size: 14px;
}

.drawer-content {
  padding: 0;
  height: 100%;
}

.error-message {
  margin: 20px;
}

.knowledge-content {
  padding: 24px;
  background: #fafafa;
  min-height: 100%;
}

.knowledge-header {
  /* margin-bottom: 24px; */
  padding-bottom: 16px;
  border-bottom: 1px solid #e0e0e0;
}

.knowledge-title {
  font-size: 20px;
  font-weight: 600;
  color: #333;
  margin: 0;
  display: flex;
  align-items: center;
  position: relative;
  padding-left: 16px;
}

.knowledge-title::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 4px;
  height: 20px;
  background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
  border-radius: 2px;
}
.knowledge-description {
  margin-bottom: 32px;
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.knowledge-description h3 {
  font-size: 16px;
  font-weight: 600;
  color: #333;
  margin: 0 0 12px 0;
}

.knowledge-description p {
  font-size: 14px;
  line-height: 1.6;
  color: #666;
  margin: 0;
}

.recommended-content {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}

.recommended-content h3 {
  font-size: 16px;
  font-weight: 600;
  color: #333;
  margin: 0;
  padding: 20px 20px 16px 20px;
  background: #fff;
  border-bottom: 1px solid #f0f0f0;
}

.content-list {
  padding: 0;
}

.content-item {
  display: flex;
  padding: 16px 20px;
  border-bottom: 1px solid #f0f0f0;
  transition: background-color 0.2s ease;
}

.content-item:last-child {
  border-bottom: none;
}

.content-item:hover {
  background-color: rgba(58, 123, 213, 0.05);
}

.item-number {
  color: #3a7bd5;
  font-size: 14px;
  font-weight: 500;
  margin-right: 8px;
  flex-shrink: 0;
  line-height: 1.5;
}

.item-content {
  flex: 1;
  min-width: 0;
}

.item-title {
  margin-bottom: 4px;
}

.title-link {
  color: #3a7bd5;
  text-decoration: none;
  font-size: 14px;
  line-height: 1.5;
  word-break: break-word;
  transition: color 0.2s ease;
}

.title-link:hover {
  color: #00d2ff;
  text-decoration: underline;
}

.item-source {
  color: #999;
  font-size: 12px;
  margin-top: 4px;
}
:deep(.el-drawer__header){
  margin-bottom: 14px;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>






























